[WIP]: /server/:key/war-stats

- add DateTimePicker
- add some basic logic to the WarStatsPage
This commit is contained in:
Dawid Wysokiński 2020-12-28 19:54:37 +01:00
parent 48e7a01355
commit 42bff4ee3a
19 changed files with 513 additions and 71 deletions

View File

@ -4,9 +4,11 @@
"private": true,
"dependencies": {
"@apollo/client": "^3.2.5",
"@date-io/date-fns": "1.3.13",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.11.0",
"@material-ui/lab": "^4.0.0-alpha.57",
"@material-ui/pickers": "3.2.10",
"@nivo/core": "0.64.0",
"@nivo/line": "0.64.0",
"@testing-library/jest-dom": "^5.11.4",

View File

@ -0,0 +1,42 @@
import React from 'react';
import clsx from 'clsx';
import {
DateTimePicker as MUIDateTimePicker,
DateTimePickerProps,
} from '@material-ui/pickers';
import { makeStyles } from '@material-ui/core/styles';
function DateTimePicker({
className,
ampm = false,
...props
}: DateTimePickerProps) {
const classes = useStyles();
return (
<MUIDateTimePicker
{...props}
ampm={ampm}
DialogProps={{ className: clsx(className, classes.dialog) }}
/>
);
}
const useStyles = makeStyles(theme => ({
dialog: {
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.secondary.main,
},
'& .MuiPickersDay-daySelected': {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
},
'& .MuiPickersDay-current': {
color: theme.palette.secondary.main,
},
'& .MuiDialogActions-root .MuiButton-root': {
color: theme.palette.secondary.contrastText,
},
},
}));
export default DateTimePicker;

View File

@ -21,6 +21,7 @@ export const TRACKING_CODE = process.env.REACT_APP_TRACKING_CODE ?? '';
export const DATE_FORMAT = {
MONTH_AND_YEAR: 'yyyy-MM',
HOUR_AND_SECOND: 'HH:mm',
DAY_MONTH_AND_YEAR: 'yyyy-MM-dd',
HOUR_MINUTES_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm',
HOUR_MINUTES_SECONDS_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm:ss',

View File

@ -9,6 +9,7 @@ export const SERVER_PAGE = {
INDEX_PAGE: 'server-page/index-page',
MAP_PAGE: 'server-page/map-page',
ENNOBLEMENTS_PAGE: 'server-page/ennoblements-page',
WAR_STATS_PAGE: 'server-page/war-stats-page',
PLAYER_PAGE: {
COMMON: 'server-page/player-page/common',
INDEX_PAGE: 'server-page/player-page/index-page',

View File

@ -37,4 +37,5 @@ export const SERVER_PAGE = {
},
ENNOBLEMENTS_PAGE: '/server/:key/ennoblements',
MAP_PAGE: '/server/:key/map',
WAR_STATS_PAGE: '/server/:key/war-stats'
};

View File

@ -10,6 +10,7 @@ import VillagePage from './features/VillagePage/VillagePage';
import RankingPage from './features/RankingPage/RankingPage';
import MapPage from './features/MapPage/MapPage';
import EnnoblementsPage from './features/EnnoblementsPage/EnnoblementsPage';
import WarStatsPage from './features/WarStatsPage/WarStatsPage';
import NotFoundPage from './features/NotFoundPage/NotFoundPage';
function ServerPage() {
@ -37,6 +38,9 @@ function ServerPage() {
<Route exact path={SERVER_PAGE.ENNOBLEMENTS_PAGE}>
<EnnoblementsPage />
</Route>
<Route exact path={SERVER_PAGE.WAR_STATS_PAGE}>
<WarStatsPage />
</Route>
<Route path="*">
<NotFoundPage />
</Route>

View File

@ -11,7 +11,6 @@ import useTitle from '@libs/useTitle';
import useServer from '../../libs/ServerContext/useServer';
import useMarkers from './useMarkers';
import ColorParam from '@libs/serialize-query-params/ColorParam';
import { encodeMarker } from './helpers';
import { MAP_SERVICE } from '@config/app';
import { SERVER_PAGE } from '@config/namespaces';
import { PLAYERS, TRIBES } from './queries';
@ -88,18 +87,6 @@ function MapPage() {
const { t } = useTranslation(SERVER_PAGE.MAP_PAGE);
useTitle(t('title', { key }));
const loading = loadingTribeMarkers || loadingPlayerMarkers;
const centerFlex = {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
};
let serverPageLayoutProps: ServerPageLayoutProps = loading
? {
noPadding: true,
contentStyle: centerFlex as React.CSSProperties,
}
: {};
const createSettingsChangeHandler = (key: keyof Settings) => (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
@ -158,6 +145,10 @@ function MapPage() {
}
};
const encodeMarker = (id: number, color: string): string => {
return encodeURIComponent(id + ',' + color);
};
const handleSubmit = (e: React.FormEvent<{}>) => {
e.preventDefault();
@ -189,11 +180,14 @@ function MapPage() {
option && value ? option.name === value.name : false;
return (
<ServerPageLayout {...serverPageLayoutProps}>
<ServerPageLayout>
{loading && (
<Spinner
containerProps={{
...centerFlex,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
height: '100%',
paddingY: 5,

View File

@ -1,3 +0,0 @@
export const encodeMarker = (id: number, color: string): string => {
return encodeURIComponent(id + ',' + color);
};

View File

@ -4,13 +4,16 @@ export type Tribe = {
id: number;
tag: string;
};
export type TribeList = {
tribes?: List<Tribe[]>;
};
export type Player = {
id: number;
name: string;
};
export type PlayerList = {
players?: List<Player[]>;
};

View File

@ -45,7 +45,7 @@ const useMarkers = <T extends HasID, VariablesT>(
const [markers, setMarkers] = useState<Marker<T>[]>([]);
useEffect(() => {
const markers: { [key: number]: string } = {};
const colorByID: { [key: number]: string } = {};
query[opts.paramName].forEach((rawStr: string | null) => {
if (!rawStr) {
return;
@ -62,34 +62,38 @@ const useMarkers = <T extends HasID, VariablesT>(
return;
}
markers[idInt] = color;
colorByID[idInt] = color;
});
const ids = Object.keys(markers).map(id => parseInt(id, 10));
const ids = Object.keys(colorByID).map(id => parseInt(id, 10));
if (ids.length > 0) {
client
.query<Record<string, List<T[]>>, VariablesT>({
query: opts.query,
variables: opts.getVariables(ids),
fetchPolicy: 'network-only',
})
.then(res => {
if (opts.dataKey in res.data && res.data[opts.dataKey]) {
setMarkers(
res.data[opts.dataKey].items.map(item => {
return getNewMarker(item, markers[item.id]);
})
);
}
})
.finally(() => {
setLoading(false);
});
loadMarkers(ids, colorByID);
} else {
setLoading(false);
} // eslint-disable-next-line
}, []);
const loadMarkers = (ids: number[], colorByID: { [key: number]: string }) => {
return client
.query<Record<string, List<T[]>>, VariablesT>({
query: opts.query,
variables: opts.getVariables(ids),
fetchPolicy: 'network-only',
})
.then(res => {
if (opts.dataKey in res.data && res.data[opts.dataKey]) {
setMarkers(
res.data[opts.dataKey].items.map(item => {
return getNewMarker(item, colorByID[item.id]);
})
);
}
})
.finally(() => {
setLoading(false);
});
};
const getNewMarker = (
item: T | null = null,
color: string = '#000000'

View File

@ -0,0 +1,133 @@
import React, { useRef } from 'react';
import { subDays } from 'date-fns';
import { useQueryParams, DateTimeParam, withDefault } from 'use-query-params';
import { useTranslation } from 'react-i18next';
import useTitle from '@libs/useTitle';
import useServer from '../../libs/ServerContext/useServer';
import useSide from './useSide';
import { SERVER_PAGE } from '@config/namespaces';
import { makeStyles } from '@material-ui/core/styles';
import { Container, Grid, Typography, TextField } from '@material-ui/core';
import DateTimePicker from '@common/Picker/DateTimePicker';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Spinner from '@common/Spinner/Spinner';
import Card from './components/Card/Card';
function WarStatsPage() {
const now = useRef(new Date());
const [query, setQuery] = useQueryParams({
ennobledAtGTE: withDefault(DateTimeParam, subDays(now.current, 1)),
ennobledAtLTE: withDefault(DateTimeParam, now.current),
});
const classes = useStyles();
const server = useServer();
const { t } = useTranslation(SERVER_PAGE.WAR_STATS_PAGE);
useTitle(t('title', { key: server.key }));
const {
players: sideOnePlayers,
tribes: sideOneTribes,
loading: sideOneLoading,
handleChangePlayers: sideOneHandleChangePlayers,
handleChangeTribes: sideOneHandleChangeTribes,
} = useSide(server.key, { paramNamePrefix: 'sideOne' });
const {
players: sideTwoPlayers,
tribes: sideTwoTribes,
loading: sideTwoLoading,
handleChangePlayers: sideTwoHandleChangePlayers,
handleChangeTribes: sideTwoHandleChangeTribes,
} = useSide(server.key, { paramNamePrefix: 'sideTwo' });
const loading = true;
return (
<ServerPageLayout>
{loading && (
<Spinner
containerProps={{
minHeight: 'inherit',
textAlign: 'center',
paddingY: 5,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
description={t('loading')}
/>
)}
{!loading && (
<Container>
<form>
<Grid container spacing={2}>
<Card>
<Typography variant="h4" align="center" gutterBottom>
{t('sections.settings')}
</Typography>
<div className={classes.formGroup}>
{[
{
name: 'ennobledAtGTE',
val: query.ennobledAtGTE,
maxDate: query.ennobledAtLTE,
},
{
name: 'ennobledAtLTE',
val: query.ennobledAtLTE,
minDate: query.ennobledAtGTE,
},
].map(({ name, val, maxDate, minDate }) => {
return (
<DateTimePicker
key={name}
maxDate={maxDate}
minDate={minDate}
disableFuture
TextFieldComponent={props => {
return (
<TextField
{...props}
helperText=""
fullWidth
variant="standard"
/>
);
}}
label={t('inputLabels.' + name)}
value={val}
format="yyyy/MM/dd HH:mm"
onChange={d => {
setQuery({ [name]: d ? d : undefined });
}}
/>
);
})}
</div>
</Card>
<Card>
<Typography variant="h4" align="center" gutterBottom>
{t('sections.sideOne')}
</Typography>
</Card>
<Card>
<Typography variant="h4" align="center" gutterBottom>
{t('sections.sideTwo')}
</Typography>
</Card>
</Grid>
</form>
</Container>
)}
</ServerPageLayout>
);
}
const useStyles = makeStyles(theme => ({
formGroup: {
'& > *': {
marginBottom: theme.spacing(1),
},
},
}));
export default WarStatsPage;

View File

@ -0,0 +1,19 @@
import React from 'react';
import { Grid, Card as MUICard, CardContent } from '@material-ui/core';
export interface Props {
children?: React.ReactNode;
}
const Card = ({ children }: Props) => {
return (
<Grid item xs={12} md={4}>
<MUICard>
<CardContent>{children}</CardContent>
</MUICard>
</Grid>
);
};
export default Card;

View File

@ -0,0 +1,47 @@
import { gql } from '@apollo/client';
export const PLAYERS = gql`
query players(
$server: String!
$filter: PlayerFilter!
$limit: Int
$offset: Int
$sort: [String!]
) {
players(
server: $server
filter: $filter
limit: $limit
offset: $offset
sort: $sort
) {
items {
id
name
}
}
}
`;
export const TRIBES = gql`
query tribes(
$server: String!
$filter: TribeFilter!
$limit: Int
$offset: Int
$sort: [String!]
) {
tribes(
server: $server
filter: $filter
limit: $limit
offset: $offset
sort: $sort
) {
items {
id
tag
}
}
}
`;

View File

@ -0,0 +1,19 @@
import { List } from '@libs/graphql/types';
export type Tribe = {
id: number;
tag: string;
};
export type TribeList = {
tribes?: List<Tribe[]>;
};
export type Player = {
id: number;
name: string;
};
export type PlayerList = {
players?: List<Player[]>;
};

View File

@ -0,0 +1,135 @@
import { useState, useEffect } from 'react';
import { useApolloClient, DocumentNode } from '@apollo/client';
import {
useQueryParams,
NumericArrayParam,
withDefault,
} from 'use-query-params';
import { PLAYERS, TRIBES } from './queries';
import {
PlayersQueryVariables,
TribesQueryVariables,
} from '@libs/graphql/types';
import { Player, Tribe, PlayerList, TribeList } from './types';
const getParamName = (
type: 'player' | 'tribe',
paramNamePrefix: string
): string => {
return type === 'player'
? `${paramNamePrefix}Player`
: `${paramNamePrefix}Tribe`;
};
export type Options = {
paramNamePrefix: string;
};
interface HasID {
id: number;
}
export type Bag = {
players: Player[];
tribes: Tribe[];
loading: boolean;
handleChangePlayers: (_e: React.ChangeEvent<{}>, players: Player[]) => void;
handleChangeTribes: (_e: React.ChangeEvent<{}>, tribes: Tribe[]) => void;
};
const useSide = (server: string, opts: Options): Bag => {
const [loading, setLoading] = useState<boolean>(true);
const [players, setPlayers] = useState<Player[]>([]);
const [tribes, setTribes] = useState<Tribe[]>([]);
const paramNamePlayer = getParamName('player', opts.paramNamePrefix);
const paramNameTribe = getParamName('tribe', opts.paramNamePrefix);
const [query, setQuery] = useQueryParams({
[paramNamePlayer]: withDefault(NumericArrayParam, []),
[paramNameTribe]: withDefault(NumericArrayParam, []),
});
const client = useApolloClient();
useEffect(() => {
const validPlayerIDs = query[paramNamePlayer].filter(
val => val && val > 0
) as number[];
const validTribeIDs = query[paramNameTribe].filter(
val => val && val > 0
) as number[];
const promises: Promise<void>[] = [];
if (validPlayerIDs.length > 0) {
promises.push(
loadData(PLAYERS, {
server,
filter: { exists: true, id: validPlayerIDs },
})
);
}
if (validTribeIDs.length > 0) {
promises.push(
loadData(TRIBES, {
server,
filter: { exists: true, id: validTribeIDs },
})
);
}
Promise.all(promises).finally(() => {
setLoading(false);
}); // eslint-disable-next-line
}, []);
const loadData = (
query: DocumentNode,
variables: PlayersQueryVariables | TribesQueryVariables
) => {
return client
.query<
PlayerList | TribeList,
PlayersQueryVariables | TribesQueryVariables
>({
query,
variables,
fetchPolicy: 'network-only',
})
.then(res => {
if ('players' in res.data && res.data.players) {
setPlayers(res.data.players.items);
}
if ('tribes' in res.data && res.data.tribes) {
setTribes(res.data.tribes.items);
}
});
};
const getIDs = (arr: HasID[]) => {
return arr.map(({ id }) => id);
};
const updateQueryParams = (key: string, ids: number[]) => {
setQuery({ [key]: ids });
};
const handleChangePlayers = (
_e: React.ChangeEvent<{}>,
players: Player[]
) => {
setPlayers(players);
updateQueryParams(paramNamePlayer, getIDs(players));
};
const handleChangeTribes = (_e: React.ChangeEvent<{}>, tribes: Tribe[]) => {
setTribes(tribes);
updateQueryParams(paramNameTribe, getIDs(tribes));
};
return {
players,
tribes,
loading,
handleChangePlayers,
handleChangeTribes,
};
};
export default useSide;

View File

@ -4,22 +4,37 @@ import { ThemeProvider } from '@material-ui/styles';
import { ApolloProvider } from '@apollo/client';
import { BrowserRouter, Route } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import { MuiPickersUtilsProvider } from '@material-ui/pickers';
import { QueryParamProvider } from 'use-query-params';
import DateFnsUtils from '@date-io/date-fns';
import App from './features/App';
import createTheme from './theme/createTheme';
import createGraphQLClient from './libs/graphql/createClient';
import initI18N from './libs/i18n/init';
import locales, { Locale } from './libs/date/locales';
import extractVersionCodeFromHostname from './utils/extractVersionCodeFromHostname';
import { URI as API_URI } from './config/api';
import { DEFAULT_LANGUAGE } from './config/app';
import reportWebVitals from './reportWebVitals';
const version = extractVersionCodeFromHostname();
const jsx = (
<BrowserRouter>
<ThemeProvider theme={createTheme()}>
<I18nextProvider i18n={initI18N()}>
<ApolloProvider client={createGraphQLClient(API_URI)}>
<QueryParamProvider ReactRouterRoute={Route}>
<App />
</QueryParamProvider>
<MuiPickersUtilsProvider
utils={DateFnsUtils}
locale={
version in locales
? locales[version as Locale]
: locales[DEFAULT_LANGUAGE as Locale]
}
>
<QueryParamProvider ReactRouterRoute={Route}>
<App />
</QueryParamProvider>
</MuiPickersUtilsProvider>
</ApolloProvider>
</I18nextProvider>
</ThemeProvider>

View File

@ -1,9 +1,14 @@
import { Locale as DFLocale } from 'date-fns';
import pl from 'date-fns/locale/pl';
import enGB from 'date-fns/locale/en-GB';
export type Locale = 'pl' | 'en';
export type Locales = {
pl: DFLocale;
en: DFLocale;
};
export type Locale = keyof Locales;
const locales = {
const locales: Locales = {
pl,
en: enGB,
};

View File

@ -6,8 +6,6 @@ import {
} from '@material-ui/core/styles';
const createTheme = (): Theme => {
const defaultTheme = createMuiTheme();
return responsiveFontSizes(
createMuiTheme({
palette: {
@ -29,30 +27,14 @@ const createTheme = (): Theme => {
color: 'secondary',
underline: 'none',
},
MuiInput: {
color: 'secondary',
},
MuiInputLabel: {
color: 'secondary',
},
},
overrides: {
MuiTooltip: {
tooltipPlacementTop: {
[defaultTheme.breakpoints.down('xs')]: {
margin: defaultTheme.spacing(2, 0),
},
},
tooltipPlacementBottom: {
[defaultTheme.breakpoints.down('xs')]: {
margin: defaultTheme.spacing(2, 0),
},
},
tooltipPlacementLeft: {
[defaultTheme.breakpoints.down('xs')]: {
margin: defaultTheme.spacing(2, 0),
},
},
tooltipPlacementRight: {
[defaultTheme.breakpoints.down('xs')]: {
margin: defaultTheme.spacing(2, 0),
},
},
},
MuiTab: {
root: {
whiteSpace: 'nowrap',

View File

@ -989,7 +989,7 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
@ -1052,6 +1052,18 @@
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
"@date-io/core@1.x", "@date-io/core@^1.3.13":
version "1.3.13"
resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa"
integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==
"@date-io/date-fns@1.3.13":
version "1.3.13"
resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-1.3.13.tgz#7798844041640ab393f7e21a7769a65d672f4735"
integrity sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA==
dependencies:
"@date-io/core" "^1.3.13"
"@emotion/hash@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
@ -1338,6 +1350,18 @@
prop-types "^15.7.2"
react-is "^16.8.0 || ^17.0.0"
"@material-ui/pickers@3.2.10":
version "3.2.10"
resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae"
integrity sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w==
dependencies:
"@babel/runtime" "^7.6.0"
"@date-io/core" "1.x"
"@types/styled-jsx" "^2.2.8"
clsx "^1.0.2"
react-transition-group "^4.0.0"
rifm "^0.7.0"
"@material-ui/styles@^4.11.2":
version "4.11.2"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.2.tgz#e70558be3f41719e8c0d63c7a3c9ae163fdc84cb"
@ -1967,6 +1991,13 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
"@types/styled-jsx@^2.2.8":
version "2.2.8"
resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234"
integrity sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg==
dependencies:
"@types/react" "*"
"@types/tapable@*", "@types/tapable@^1.0.5":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
@ -3471,7 +3502,7 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
clsx@^1.0.4, clsx@^1.1.1:
clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
@ -9901,7 +9932,7 @@ react-spring@^8.0.27:
"@babel/runtime" "^7.3.1"
prop-types "^15.5.8"
react-transition-group@^4.4.0:
react-transition-group@^4.0.0, react-transition-group@^4.4.0:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
@ -10313,6 +10344,13 @@ rgba-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
rifm@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be"
integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==
dependencies:
"@babel/runtime" "^7.3.1"
rimraf@2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"