Add a login barrier

This commit is contained in:
Fredrik Adelöw
2020-02-05 15:57:46 +01:00
parent a6d4681134
commit a63d3af1d7
11 changed files with 428 additions and 43 deletions
+4 -1
View File
@@ -16,10 +16,13 @@
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^5.1.3",
"@types/zen-observable": "^0.8.0",
"cross-env": "^7.0.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2"
"react-router-dom": "^5.1.2",
"react-use": "^13.24.0",
"zen-observable": "^0.8.15"
},
"workspaces": {
"nohoist": [
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
describe('App', () => {
it('renders learn react link', () => {
const rendered = render(<App />);
rendered.getByText('This is Backstage!');
});
});
+20 -22
View File
@@ -11,15 +11,12 @@ import { LoginComponent } from '@backstage/plugin-login';
import HomePagePlugin from '@backstage/plugin-home-page';
import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core';
import React, { FC } from 'react';
import {
BrowserRouter as Router,
Link as RouterLink,
Route,
Switch,
} from 'react-router-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import HomePageTimer from './components/HomepageTimer';
import SideBar from './components/SideBar';
import entities from './entities';
import { LoginBarrier } from './login/LoginBarrier';
import { MockCurrentUser } from './login/MockCurrentUser';
const useStyles = makeStyles(theme => ({
'@global': {
@@ -56,13 +53,12 @@ const useStyles = makeStyles(theme => ({
},
}));
const currentUser = new MockCurrentUser();
const Login: FC<{}> = () => {
return (
<InfoCard title="Login Page">
<LoginComponent />
<div>
<RouterLink to="/">Go to Home</RouterLink>
</div>
<LoginComponent onLogin={username => currentUser.login(username)} />
</InfoCard>
);
};
@@ -96,18 +92,20 @@ const App: FC<{}> = () => {
return (
<CssBaseline>
<ThemeProvider theme={BackstageTheme}>
<AppShell>
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route>
<AppComponent />
</Route>
</Switch>
</Router>
</AppShell>
<LoginBarrier fallback={Login} state$={currentUser.state}>
<AppShell>
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route>
<AppComponent />
</Route>
</Switch>
</Router>
</AppShell>
</LoginBarrier>
</ThemeProvider>
</CssBaseline>
);
@@ -0,0 +1,57 @@
import { render, wait, act } from '@testing-library/react';
import React from 'react';
import Observable from 'zen-observable';
import { LoginBarrier } from './LoginBarrier';
import { LoginState } from './types';
describe('LoginBarrier', () => {
it('passes through when logged in', async () => {
const state$ = Observable.of<LoginState>({
type: 'LOGGED_IN',
user: 'apa',
});
const rendered = render(
<LoginBarrier
state$={state$}
fallback={({ state }) => <div>{state.type}</div>}
>
Logged in!
</LoginBarrier>,
);
await wait(() => rendered.getByText('Logged in!'));
});
it('goes to fallback when not logged in', async () => {
const state$ = Observable.of<LoginState>({ type: 'LOGGED_OUT' });
const rendered = render(
<LoginBarrier
state$={state$}
fallback={({ state }) => <div>{state.type}</div>}
>
Logged in!
</LoginBarrier>,
);
await wait(() => rendered.getByText('LOGGED_OUT'));
});
it('transitions between states', async () => {
let subscriber: ZenObservable.SubscriptionObserver<LoginState> | undefined;
const state$ = new Observable<LoginState>(s => {
subscriber = s;
});
const rendered = render(
<LoginBarrier
state$={state$}
fallback={({ state }) => <div>{state.type}</div>}
>
Logged in!
</LoginBarrier>,
);
await wait(() => rendered.getByText('LOGGED_OUT'));
// @ts-ignore: Object is possibly 'undefined'
act(() => subscriber.next({ type: 'LOGGED_IN', user: 'apa' }));
await wait(() => rendered.getByText('Logged in!'));
});
});
@@ -0,0 +1,22 @@
import React, { FC } from 'react';
import Observable from 'zen-observable';
import { useObservable } from 'react-use';
import { LoginState } from './types';
type LoginBarrierProps = {
fallback: React.ComponentType<{ state: LoginState }>;
state$: Observable<LoginState>;
};
export const LoginBarrier: FC<LoginBarrierProps> = ({
fallback: Fallback,
state$,
children,
}) => {
const state = useObservable(state$, { type: 'LOGGED_OUT' });
if (state.type === 'LOGGED_IN') {
return <>{children}</>;
} else {
return <Fallback state={state} />;
}
};
@@ -0,0 +1,38 @@
import { MockCurrentUser } from './MockCurrentUser';
import { wait } from '@testing-library/react';
describe('MockCurrentUser', () => {
it('notifies immediately on subscribe', async () => {
const next = jest.fn();
const current = new MockCurrentUser();
current.state.subscribe(next);
await wait();
expect(next).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ type: 'LOGGED_OUT' }),
);
});
it('logs in and out', async () => {
const next = jest.fn();
const current = new MockCurrentUser();
current.state.subscribe(next);
await wait();
expect(next).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ type: 'LOGGED_OUT' }),
);
current.login('apa');
await wait();
expect(next).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ type: 'LOGGED_IN', user: 'apa' }),
);
current.logout();
await wait();
expect(next).toHaveBeenNthCalledWith(
3,
expect.objectContaining({ type: 'LOGGED_OUT' }),
);
});
});
@@ -0,0 +1,38 @@
import Observable from 'zen-observable';
import { LoginState } from './types';
export class MockCurrentUser {
private listeners: ZenObservable.SubscriptionObserver<LoginState>[] = [];
private currentUser: string | undefined = undefined;
login(user: string) {
this.currentUser = user;
this.notify();
}
logout() {
this.currentUser = undefined;
this.notify();
}
get state(): Observable<LoginState> {
return new Observable(subscriber => {
this.listeners.push(subscriber);
subscriber.next(this.getState());
return () => {
this.listeners = this.listeners.filter(x => x !== subscriber);
};
});
}
private notify() {
const state = this.getState();
this.listeners.forEach(listener => listener.next(state));
}
private getState(): LoginState {
return this.currentUser
? { type: 'LOGGED_IN', user: this.currentUser }
: { type: 'LOGGED_OUT' };
}
}
+11
View File
@@ -0,0 +1,11 @@
export type LoggedOutState = { type: 'LOGGED_OUT' };
export type LoggingInState = { type: 'LOGGING_IN' };
export type LoggedInState = { type: 'LOGGED_IN'; user: string };
export type LoggingOutState = { type: 'LOGGING_OUT' };
export type LoginFailedState = { type: 'LOGIN_FAILED'; error: Error };
export type LoginState =
| LoggedOutState
| LoggingInState
| LoggedInState
| LoggingOutState
| LoginFailedState;
@@ -4,7 +4,8 @@ import LoginComponent from './LoginComponent';
describe('LoginComponent', () => {
it('should render', () => {
const rendered = render(<LoginComponent />);
const onLogin = jest.fn();
const rendered = render(<LoginComponent onLogin={onLogin} />);
expect(rendered.getByText('Login')).toBeInTheDocument();
});
});
@@ -1,11 +1,36 @@
import React, { FC, Fragment } from 'react';
import React, { FC, useState } from 'react';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Grid from '@material-ui/core/Grid';
import { Typography } from '@material-ui/core';
type Props = {
onLogin: (username: string) => Promise<void> | void;
};
const LoginComponent: FC<Props> = ({ onLogin }) => {
const [error, setError] = useState('');
const [username, setUsername] = useState('');
const onUsernameChanged = async (e: React.ChangeEvent<HTMLInputElement>) => {
setUsername(e.target.value);
};
const onSubmit = async () => {
if (!username) {
setError('You have to supply a username');
}
setError('');
try {
await onLogin(username);
} catch (e) {
setError(`Login failed: ${e.message}`);
}
};
const LoginComponent: FC<{}> = () => {
return (
<Fragment>
<form onSubmit={onSubmit} autoComplete="off">
<Grid container spacing={8} alignItems="flex-end">
<Grid item md={true} sm={true} xs={true}>
<TextField
@@ -14,6 +39,8 @@ const LoginComponent: FC<{}> = () => {
type="email"
autoFocus
required
value={username}
onChange={onUsernameChanged}
/>
</Grid>
</Grid>
@@ -23,14 +50,17 @@ const LoginComponent: FC<{}> = () => {
style={{ marginTop: '24px', marginBottom: '24px' }}
>
<Button
type="submit"
variant="contained"
color="primary"
style={{ textTransform: 'none' }}
onClick={onSubmit}
disabled={!username}
>
Login
</Button>
</Grid>
</Fragment>
<Typography>{error || 'Just enter any fake username'}</Typography>
</form>
);
};
+201 -4
View File
@@ -2890,6 +2890,11 @@
jest-diff "^25.1.0"
pretty-format "^25.1.0"
"@types/js-cookie@2.2.4":
version "2.2.4"
resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.4.tgz#f79720b4755aa197c2e15e982e2f438f5748e348"
integrity sha512-WTfSE1Eauak/Nrg6cA9FgPTFvVawejsai6zXoq0QYTQ3mxONeRtGhKxa7wMlUzWWmzrmTeV+rwLjHgsCntdrsA==
"@types/json-schema@^7.0.3":
version "7.0.4"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
@@ -3013,6 +3018,11 @@
dependencies:
"@types/yargs-parser" "*"
"@types/zen-observable@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
"@typescript-eslint/eslint-plugin@^2.14.0", "@typescript-eslint/eslint-plugin@^2.8.0":
version "2.18.0"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.18.0.tgz#f8cf272dfb057ecf1ea000fea1e0b3f06a32f9cb"
@@ -3202,6 +3212,11 @@
"@webassemblyjs/wast-parser" "1.8.5"
"@xtuc/long" "4.2.2"
"@xobotyi/scrollbar-width@1.8.2":
version "1.8.2"
resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.8.2.tgz#056946ac41ade4885c576619c8d70c46c77e9683"
integrity sha512-RV6+4hR29oMaPCvSYFUvzOvlsrg2s2k5NE9tNERs+4nFIC9dRXxs+lL2CcaRTbl3yQxKwAZ8Cd+qMI8aUu9TFw==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -4033,6 +4048,11 @@ bottleneck@^2.18.1:
resolved "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91"
integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==
bowser@^1.7.3:
version "1.9.4"
resolved "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a"
integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==
boxen@^1.2.1:
version "1.3.0"
resolved "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
@@ -5115,6 +5135,13 @@ copy-descriptor@^0.1.0:
resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-to-clipboard@^3.2.0:
version "3.2.1"
resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.1.tgz#b1a1137100e5665d5a96015cb579e30e90e07c44"
integrity sha512-btru1Q6RD9wbonIvEU5EfnhIRGHLo//BGXQ1hNAD2avIs/nBZlpbOeKtv3mhoUByN4DB9Cb6/vXBymj1S43KmA==
dependencies:
toggle-selection "^1.0.6"
core-js-compat@^3.6.2:
version "3.6.4"
resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17"
@@ -5300,6 +5327,14 @@ css-has-pseudo@^0.10.0:
postcss "^7.0.6"
postcss-selector-parser "^5.0.0-rc.4"
css-in-js-utils@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99"
integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==
dependencies:
hyphenate-style-name "^1.0.2"
isobject "^3.0.1"
css-loader@3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/css-loader/-/css-loader-3.2.0.tgz#bb570d89c194f763627fcf1f80059c6832d009b2"
@@ -5358,6 +5393,14 @@ css-tree@1.0.0-alpha.37:
mdn-data "2.0.4"
source-map "^0.6.1"
css-tree@^1.0.0-alpha.28:
version "1.0.0-alpha.39"
resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb"
integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==
dependencies:
mdn-data "2.0.6"
source-map "^0.6.1"
css-unit-converter@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996"
@@ -5510,7 +5553,7 @@ cssstyle@^2.0.0:
dependencies:
cssom "~0.3.6"
csstype@^2.2.0, csstype@^2.5.2, csstype@^2.6.5, csstype@^2.6.7:
csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.6.5, csstype@^2.6.7:
version "2.6.8"
resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==
@@ -6154,6 +6197,13 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
error-stack-parser@^2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8"
integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==
dependencies:
stackframe "^1.1.1"
es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2:
version "1.17.4"
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
@@ -6758,6 +6808,16 @@ fast-levenshtein@~2.0.6:
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-shallow-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b"
integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==
fastest-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028"
integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg=
fastq@^1.6.0:
version "1.6.0"
resolved "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2"
@@ -7947,7 +8007,7 @@ humanize-ms@^1.2.1:
dependencies:
ms "^2.0.0"
hyphenate-style-name@^1.0.3:
hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48"
integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==
@@ -8147,6 +8207,14 @@ init-package-json@^1.10.3:
validate-npm-package-license "^3.0.1"
validate-npm-package-name "^3.0.0"
inline-style-prefixer@^4.0.0:
version "4.0.2"
resolved "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911"
integrity sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg==
dependencies:
bowser "^1.7.3"
css-in-js-utils "^2.0.0"
inquirer@6.5.0:
version "6.5.0"
resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
@@ -9541,6 +9609,11 @@ jest@^25.1.0:
import-local "^3.0.2"
jest-cli "^25.1.0"
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -10588,6 +10661,11 @@ mdn-data@2.0.4:
resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
mdn-data@2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978"
integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==
meant@~1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/meant/-/meant-1.0.1.tgz#66044fea2f23230ec806fb515efea29c44d2115d"
@@ -11019,6 +11097,20 @@ nan@^2.12.1:
resolved "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
nano-css@^5.2.1:
version "5.2.1"
resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.2.1.tgz#73b8470fa40b028a134d3393ae36bbb34b9fa332"
integrity sha512-T54okxMAha0+de+W8o3qFtuWhTxYvqQh2ku1cYEqTTP9mR62nWV2lLK9qRuAGWmoaYWhU7K4evT9Lc1iF65wuw==
dependencies:
css-tree "^1.0.0-alpha.28"
csstype "^2.5.5"
fastest-stable-stringify "^1.0.1"
inline-style-prefixer "^4.0.0"
rtl-css-js "^1.9.0"
sourcemap-codec "^1.4.1"
stacktrace-js "^2.0.0"
stylis "3.5.0"
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -13360,7 +13452,7 @@ react-error-overlay@^6.0.5:
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.5.tgz#55d59c2a3810e8b41922e0b4e5f85dcf239bd533"
integrity sha512-+DMR2k5c6BqMDSMF8hLH0vYKtKTeikiFW+fj0LClN+XZg4N9b8QUAdHC62CGWNLTi/gnuuemNcNcTFrCvK1f+A==
react-fast-compare@^2.0.2:
react-fast-compare@^2.0.2, react-fast-compare@^2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
@@ -13431,6 +13523,25 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-use@^13.24.0:
version "13.24.0"
resolved "https://registry.npmjs.org/react-use/-/react-use-13.24.0.tgz#f4574e26cfaaad65e3f04c0d5ff80c1836546236"
integrity sha512-p8GsZuMdz8OeIGzuYLm6pzJysKOhNyQjCUG6SHrQGk6o6ghy/RVGSqnmxVacNbN9166S0+9FsM1N1yH9GzWlgg==
dependencies:
"@types/js-cookie" "2.2.4"
"@xobotyi/scrollbar-width" "1.8.2"
copy-to-clipboard "^3.2.0"
fast-shallow-equal "^1.0.0"
js-cookie "^2.2.1"
nano-css "^5.2.1"
react-fast-compare "^2.0.4"
resize-observer-polyfill "^1.5.1"
screenfull "^5.0.0"
set-harmonic-interval "^1.0.1"
throttle-debounce "^2.1.0"
ts-easing "^0.2.0"
tslib "^1.10.0"
react@^16.12.0:
version "16.12.0"
resolved "https://registry.npmjs.org/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83"
@@ -13900,6 +14011,11 @@ requires-port@^1.0.0:
resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -14087,6 +14203,13 @@ rsvp@^4.8.4:
resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
rtl-css-js@^1.9.0:
version "1.14.0"
resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.14.0.tgz#daa4f192a92509e292a0519f4b255e6e3c076b7d"
integrity sha512-Dl5xDTeN3e7scU1cWX8c9b6/Nqz3u/HgR4gePc1kWXYiQWVQbKCEyK6+Hxve9LbcJ5EieHy1J9nJCN3grTtGwg==
dependencies:
"@babel/runtime" "^7.1.2"
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
@@ -14203,6 +14326,11 @@ schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.1.0, schema-utils@^2.2
ajv "^6.10.2"
ajv-keywords "^3.4.1"
screenfull@^5.0.0:
version "5.0.1"
resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.0.1.tgz#873052411eb9096bc1c8e615f5badad119e6e42c"
integrity sha512-NgQH4KKh2V3zlj2u90l7TUcSFxr9qL/64QEvhAvCN/fu1YS39YLTBKIqZqiS3STj3QD8sN6XnsK/8jk3hRq4WA==
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -14345,6 +14473,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
set-harmonic-interval@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249"
integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@@ -14645,6 +14778,11 @@ source-map-url@^0.4.0:
resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@0.5.6:
version "0.5.6"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
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.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
@@ -14660,6 +14798,11 @@ source-map@^0.7.3:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.1:
version "1.4.8"
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
spawn-error-forwarder@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz#1afd94738e999b0346d7b9fc373be55e07577029"
@@ -14787,11 +14930,40 @@ stable@^0.1.8:
resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
stack-generator@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36"
integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==
dependencies:
stackframe "^1.1.1"
stack-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
stackframe@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz#ffef0a3318b1b60c3b58564989aca5660729ec71"
integrity sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ==
stacktrace-gps@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a"
integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==
dependencies:
source-map "0.5.6"
stackframe "^1.1.1"
stacktrace-js@^2.0.0:
version "2.0.2"
resolved "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b"
integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==
dependencies:
error-stack-parser "^2.0.6"
stack-generator "^2.0.5"
stacktrace-gps "^3.0.4"
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -15103,6 +15275,11 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
stylis@3.5.0:
version "3.5.0"
resolved "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw==
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -15331,6 +15508,11 @@ throat@^5.0.0:
resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
throttle-debounce@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5"
integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg==
through2@^2.0.0, through2@^2.0.2, through2@~2.0.0:
version "2.0.5"
resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
@@ -15442,6 +15624,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@@ -15499,6 +15686,11 @@ trim-off-newlines@^1.0.0:
resolved "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM=
ts-easing@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
ts-jest@^25.0.0:
version "25.1.0"
resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-25.1.0.tgz#06e776c4cce8a4da8eec4945f36a5823d0c0f9ba"
@@ -15520,7 +15712,7 @@ ts-pnp@1.1.5, ts-pnp@^1.1.2:
resolved "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.5.tgz#840e0739c89fce5f3abd9037bb091dbff16d9dec"
integrity sha512-ti7OGMOUOzo66wLF3liskw6YQIaSsBgc4GOAlWRnIEj8htCxJUxskanMUoJOD6MDCRAXo36goXJZch+nOS0VMA==
tslib@^1.8.1, tslib@^1.9.0:
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
@@ -16691,3 +16883,8 @@ yargs@^8.0.2:
which-module "^2.0.0"
y18n "^3.2.1"
yargs-parser "^7.0.0"
zen-observable@^0.8.15:
version "0.8.15"
resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==