[WIP]: /server/:key/war-stats
- add DateTimePicker - add some basic logic to the WarStatsPage
This commit is contained in:
parent
48e7a01355
commit
42bff4ee3a
|
@ -4,9 +4,11 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.2.5",
|
"@apollo/client": "^3.2.5",
|
||||||
|
"@date-io/date-fns": "1.3.13",
|
||||||
"@material-ui/core": "^4.11.0",
|
"@material-ui/core": "^4.11.0",
|
||||||
"@material-ui/icons": "^4.11.0",
|
"@material-ui/icons": "^4.11.0",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||||
|
"@material-ui/pickers": "3.2.10",
|
||||||
"@nivo/core": "0.64.0",
|
"@nivo/core": "0.64.0",
|
||||||
"@nivo/line": "0.64.0",
|
"@nivo/line": "0.64.0",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
|
|
42
src/common/Picker/DateTimePicker.tsx
Normal file
42
src/common/Picker/DateTimePicker.tsx
Normal 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;
|
|
@ -21,6 +21,7 @@ export const TRACKING_CODE = process.env.REACT_APP_TRACKING_CODE ?? '';
|
||||||
|
|
||||||
export const DATE_FORMAT = {
|
export const DATE_FORMAT = {
|
||||||
MONTH_AND_YEAR: 'yyyy-MM',
|
MONTH_AND_YEAR: 'yyyy-MM',
|
||||||
|
HOUR_AND_SECOND: 'HH:mm',
|
||||||
DAY_MONTH_AND_YEAR: 'yyyy-MM-dd',
|
DAY_MONTH_AND_YEAR: 'yyyy-MM-dd',
|
||||||
HOUR_MINUTES_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm',
|
HOUR_MINUTES_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm',
|
||||||
HOUR_MINUTES_SECONDS_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm:ss',
|
HOUR_MINUTES_SECONDS_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm:ss',
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const SERVER_PAGE = {
|
||||||
INDEX_PAGE: 'server-page/index-page',
|
INDEX_PAGE: 'server-page/index-page',
|
||||||
MAP_PAGE: 'server-page/map-page',
|
MAP_PAGE: 'server-page/map-page',
|
||||||
ENNOBLEMENTS_PAGE: 'server-page/ennoblements-page',
|
ENNOBLEMENTS_PAGE: 'server-page/ennoblements-page',
|
||||||
|
WAR_STATS_PAGE: 'server-page/war-stats-page',
|
||||||
PLAYER_PAGE: {
|
PLAYER_PAGE: {
|
||||||
COMMON: 'server-page/player-page/common',
|
COMMON: 'server-page/player-page/common',
|
||||||
INDEX_PAGE: 'server-page/player-page/index-page',
|
INDEX_PAGE: 'server-page/player-page/index-page',
|
||||||
|
|
|
@ -37,4 +37,5 @@ export const SERVER_PAGE = {
|
||||||
},
|
},
|
||||||
ENNOBLEMENTS_PAGE: '/server/:key/ennoblements',
|
ENNOBLEMENTS_PAGE: '/server/:key/ennoblements',
|
||||||
MAP_PAGE: '/server/:key/map',
|
MAP_PAGE: '/server/:key/map',
|
||||||
|
WAR_STATS_PAGE: '/server/:key/war-stats'
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import VillagePage from './features/VillagePage/VillagePage';
|
||||||
import RankingPage from './features/RankingPage/RankingPage';
|
import RankingPage from './features/RankingPage/RankingPage';
|
||||||
import MapPage from './features/MapPage/MapPage';
|
import MapPage from './features/MapPage/MapPage';
|
||||||
import EnnoblementsPage from './features/EnnoblementsPage/EnnoblementsPage';
|
import EnnoblementsPage from './features/EnnoblementsPage/EnnoblementsPage';
|
||||||
|
import WarStatsPage from './features/WarStatsPage/WarStatsPage';
|
||||||
import NotFoundPage from './features/NotFoundPage/NotFoundPage';
|
import NotFoundPage from './features/NotFoundPage/NotFoundPage';
|
||||||
|
|
||||||
function ServerPage() {
|
function ServerPage() {
|
||||||
|
@ -37,6 +38,9 @@ function ServerPage() {
|
||||||
<Route exact path={SERVER_PAGE.ENNOBLEMENTS_PAGE}>
|
<Route exact path={SERVER_PAGE.ENNOBLEMENTS_PAGE}>
|
||||||
<EnnoblementsPage />
|
<EnnoblementsPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route exact path={SERVER_PAGE.WAR_STATS_PAGE}>
|
||||||
|
<WarStatsPage />
|
||||||
|
</Route>
|
||||||
<Route path="*">
|
<Route path="*">
|
||||||
<NotFoundPage />
|
<NotFoundPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -11,7 +11,6 @@ import useTitle from '@libs/useTitle';
|
||||||
import useServer from '../../libs/ServerContext/useServer';
|
import useServer from '../../libs/ServerContext/useServer';
|
||||||
import useMarkers from './useMarkers';
|
import useMarkers from './useMarkers';
|
||||||
import ColorParam from '@libs/serialize-query-params/ColorParam';
|
import ColorParam from '@libs/serialize-query-params/ColorParam';
|
||||||
import { encodeMarker } from './helpers';
|
|
||||||
import { MAP_SERVICE } from '@config/app';
|
import { MAP_SERVICE } from '@config/app';
|
||||||
import { SERVER_PAGE } from '@config/namespaces';
|
import { SERVER_PAGE } from '@config/namespaces';
|
||||||
import { PLAYERS, TRIBES } from './queries';
|
import { PLAYERS, TRIBES } from './queries';
|
||||||
|
@ -88,18 +87,6 @@ function MapPage() {
|
||||||
const { t } = useTranslation(SERVER_PAGE.MAP_PAGE);
|
const { t } = useTranslation(SERVER_PAGE.MAP_PAGE);
|
||||||
useTitle(t('title', { key }));
|
useTitle(t('title', { key }));
|
||||||
const loading = loadingTribeMarkers || loadingPlayerMarkers;
|
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) => (
|
const createSettingsChangeHandler = (key: keyof Settings) => (
|
||||||
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
|
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<{}>) => {
|
const handleSubmit = (e: React.FormEvent<{}>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -189,11 +180,14 @@ function MapPage() {
|
||||||
option && value ? option.name === value.name : false;
|
option && value ? option.name === value.name : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerPageLayout {...serverPageLayoutProps}>
|
<ServerPageLayout>
|
||||||
{loading && (
|
{loading && (
|
||||||
<Spinner
|
<Spinner
|
||||||
containerProps={{
|
containerProps={{
|
||||||
...centerFlex,
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
paddingY: 5,
|
paddingY: 5,
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const encodeMarker = (id: number, color: string): string => {
|
|
||||||
return encodeURIComponent(id + ',' + color);
|
|
||||||
};
|
|
|
@ -4,13 +4,16 @@ export type Tribe = {
|
||||||
id: number;
|
id: number;
|
||||||
tag: string;
|
tag: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TribeList = {
|
export type TribeList = {
|
||||||
tribes?: List<Tribe[]>;
|
tribes?: List<Tribe[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Player = {
|
export type Player = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PlayerList = {
|
export type PlayerList = {
|
||||||
players?: List<Player[]>;
|
players?: List<Player[]>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -45,7 +45,7 @@ const useMarkers = <T extends HasID, VariablesT>(
|
||||||
const [markers, setMarkers] = useState<Marker<T>[]>([]);
|
const [markers, setMarkers] = useState<Marker<T>[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const markers: { [key: number]: string } = {};
|
const colorByID: { [key: number]: string } = {};
|
||||||
query[opts.paramName].forEach((rawStr: string | null) => {
|
query[opts.paramName].forEach((rawStr: string | null) => {
|
||||||
if (!rawStr) {
|
if (!rawStr) {
|
||||||
return;
|
return;
|
||||||
|
@ -62,34 +62,38 @@ const useMarkers = <T extends HasID, VariablesT>(
|
||||||
return;
|
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) {
|
if (ids.length > 0) {
|
||||||
client
|
loadMarkers(ids, colorByID);
|
||||||
.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);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} // eslint-disable-next-line
|
} // 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 = (
|
const getNewMarker = (
|
||||||
item: T | null = null,
|
item: T | null = null,
|
||||||
color: string = '#000000'
|
color: string = '#000000'
|
||||||
|
|
133
src/features/ServerPage/features/WarStatsPage/WarStatsPage.tsx
Normal file
133
src/features/ServerPage/features/WarStatsPage/WarStatsPage.tsx
Normal 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;
|
|
@ -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;
|
47
src/features/ServerPage/features/WarStatsPage/queries.ts
Normal file
47
src/features/ServerPage/features/WarStatsPage/queries.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
19
src/features/ServerPage/features/WarStatsPage/types.ts
Normal file
19
src/features/ServerPage/features/WarStatsPage/types.ts
Normal 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[]>;
|
||||||
|
};
|
135
src/features/ServerPage/features/WarStatsPage/useSide.ts
Normal file
135
src/features/ServerPage/features/WarStatsPage/useSide.ts
Normal 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;
|
|
@ -4,22 +4,37 @@ import { ThemeProvider } from '@material-ui/styles';
|
||||||
import { ApolloProvider } from '@apollo/client';
|
import { ApolloProvider } from '@apollo/client';
|
||||||
import { BrowserRouter, Route } from 'react-router-dom';
|
import { BrowserRouter, Route } from 'react-router-dom';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
import { MuiPickersUtilsProvider } from '@material-ui/pickers';
|
||||||
import { QueryParamProvider } from 'use-query-params';
|
import { QueryParamProvider } from 'use-query-params';
|
||||||
|
import DateFnsUtils from '@date-io/date-fns';
|
||||||
import App from './features/App';
|
import App from './features/App';
|
||||||
import createTheme from './theme/createTheme';
|
import createTheme from './theme/createTheme';
|
||||||
import createGraphQLClient from './libs/graphql/createClient';
|
import createGraphQLClient from './libs/graphql/createClient';
|
||||||
import initI18N from './libs/i18n/init';
|
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 { URI as API_URI } from './config/api';
|
||||||
|
import { DEFAULT_LANGUAGE } from './config/app';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
const version = extractVersionCodeFromHostname();
|
||||||
const jsx = (
|
const jsx = (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ThemeProvider theme={createTheme()}>
|
<ThemeProvider theme={createTheme()}>
|
||||||
<I18nextProvider i18n={initI18N()}>
|
<I18nextProvider i18n={initI18N()}>
|
||||||
<ApolloProvider client={createGraphQLClient(API_URI)}>
|
<ApolloProvider client={createGraphQLClient(API_URI)}>
|
||||||
<QueryParamProvider ReactRouterRoute={Route}>
|
<MuiPickersUtilsProvider
|
||||||
<App />
|
utils={DateFnsUtils}
|
||||||
</QueryParamProvider>
|
locale={
|
||||||
|
version in locales
|
||||||
|
? locales[version as Locale]
|
||||||
|
: locales[DEFAULT_LANGUAGE as Locale]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<QueryParamProvider ReactRouterRoute={Route}>
|
||||||
|
<App />
|
||||||
|
</QueryParamProvider>
|
||||||
|
</MuiPickersUtilsProvider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
import { Locale as DFLocale } from 'date-fns';
|
||||||
import pl from 'date-fns/locale/pl';
|
import pl from 'date-fns/locale/pl';
|
||||||
import enGB from 'date-fns/locale/en-GB';
|
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,
|
pl,
|
||||||
en: enGB,
|
en: enGB,
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,6 @@ import {
|
||||||
} from '@material-ui/core/styles';
|
} from '@material-ui/core/styles';
|
||||||
|
|
||||||
const createTheme = (): Theme => {
|
const createTheme = (): Theme => {
|
||||||
const defaultTheme = createMuiTheme();
|
|
||||||
|
|
||||||
return responsiveFontSizes(
|
return responsiveFontSizes(
|
||||||
createMuiTheme({
|
createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
|
@ -29,30 +27,14 @@ const createTheme = (): Theme => {
|
||||||
color: 'secondary',
|
color: 'secondary',
|
||||||
underline: 'none',
|
underline: 'none',
|
||||||
},
|
},
|
||||||
|
MuiInput: {
|
||||||
|
color: 'secondary',
|
||||||
|
},
|
||||||
|
MuiInputLabel: {
|
||||||
|
color: 'secondary',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
overrides: {
|
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: {
|
MuiTab: {
|
||||||
root: {
|
root: {
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
|
|
44
yarn.lock
44
yarn.lock
|
@ -989,7 +989,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
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"
|
version "7.12.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
||||||
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
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"
|
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
|
||||||
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
|
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":
|
"@emotion/hash@^0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
|
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
|
||||||
|
@ -1338,6 +1350,18 @@
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
react-is "^16.8.0 || ^17.0.0"
|
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":
|
"@material-ui/styles@^4.11.2":
|
||||||
version "4.11.2"
|
version "4.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.2.tgz#e70558be3f41719e8c0d63c7a3c9ae163fdc84cb"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
|
||||||
integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
|
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":
|
"@types/tapable@*", "@types/tapable@^1.0.5":
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
|
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"
|
kind-of "^6.0.2"
|
||||||
shallow-clone "^3.0.0"
|
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"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||||
|
@ -9901,7 +9932,7 @@ react-spring@^8.0.27:
|
||||||
"@babel/runtime" "^7.3.1"
|
"@babel/runtime" "^7.3.1"
|
||||||
prop-types "^15.5.8"
|
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"
|
version "4.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
|
||||||
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
|
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"
|
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
|
||||||
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
|
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:
|
rimraf@2.6.3:
|
||||||
version "2.6.3"
|
version "2.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||||
|
|
Reference in New Issue
Block a user