Optimize a few more components
This commit is contained in:
parent
1bf7a62c82
commit
2eaf382b67
|
@ -33,13 +33,15 @@ import isArray from 'lodash/isArray';
|
||||||
import range from 'lodash/range';
|
import range from 'lodash/range';
|
||||||
import { DropzoneDialog } from 'material-ui-dropzone';
|
import { DropzoneDialog } from 'material-ui-dropzone';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import isEqual from 'react-fast-compare';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import { DeepReadonly } from 'ts-essentials';
|
||||||
|
|
||||||
import { isDefined, nameofFactory, noComplete } from '../common';
|
import { isDefined, nameofFactory, noComplete } from '../common';
|
||||||
import { Board } from '../components/board';
|
import { Board } from '../components/board';
|
||||||
import { ClipboardButton } from '../components/clipboard';
|
import { ClipboardButton } from '../components/clipboard';
|
||||||
import { useServerTime } from '../hooks';
|
import { useServerTime } from '../hooks';
|
||||||
import { State, StatePlayer, StateTimer, WordPack } from '../protocol';
|
import { State, StatePlayer, StateTeams, StateTimer, StateWordList, WordPack } from '../protocol';
|
||||||
import { teamSpecs } from '../teams';
|
import { teamSpecs } from '../teams';
|
||||||
|
|
||||||
export interface Sender {
|
export interface Sender {
|
||||||
|
@ -58,15 +60,6 @@ export interface Sender {
|
||||||
changeHideBomb: (HideBomb: boolean) => void;
|
changeHideBomb: (HideBomb: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameViewProps {
|
|
||||||
roomID: string;
|
|
||||||
leave: () => void;
|
|
||||||
send: Sender;
|
|
||||||
state: State;
|
|
||||||
pState: StatePlayer;
|
|
||||||
pTeam: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useCenterStyles = makeStyles((_theme: Theme) =>
|
const useCenterStyles = makeStyles((_theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
blink: {
|
blink: {
|
||||||
|
@ -351,14 +344,29 @@ const useSidebarStyles = makeStyles((_theme: Theme) =>
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const Sidebar = ({ send, state, pState, pTeam }: GameViewProps) => {
|
interface SidebarProps {
|
||||||
|
send: Sender;
|
||||||
|
teams: StateTeams;
|
||||||
|
lists: StateWordList[];
|
||||||
|
pTeam: number;
|
||||||
|
playerID: string;
|
||||||
|
version: number;
|
||||||
|
timer: StateTimer | undefined | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sidebar = React.memo(function Sidebar({
|
||||||
|
send,
|
||||||
|
teams,
|
||||||
|
lists,
|
||||||
|
pTeam,
|
||||||
|
playerID,
|
||||||
|
version,
|
||||||
|
timer,
|
||||||
|
}: DeepReadonly<SidebarProps>) {
|
||||||
const classes = useSidebarStyles();
|
const classes = useSidebarStyles();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const nameShade = theme.palette.type === 'dark' ? 400 : 600;
|
const nameShade = theme.palette.type === 'dark' ? 400 : 600;
|
||||||
|
|
||||||
const teams = state.teams;
|
|
||||||
const lists = state.lists;
|
|
||||||
|
|
||||||
const wordCount = React.useMemo(
|
const wordCount = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
lists.reduce((curr, l) => {
|
lists.reduce((curr, l) => {
|
||||||
|
@ -408,7 +416,7 @@ const Sidebar = ({ send, state, pState, pTeam }: GameViewProps) => {
|
||||||
gridRow: j + 2,
|
gridRow: j + 2,
|
||||||
gridColumn: i + 1,
|
gridColumn: i + 1,
|
||||||
color: teamSpecs[i].hue[nameShade],
|
color: teamSpecs[i].hue[nameShade],
|
||||||
fontStyle: member.playerID === pState.playerID ? 'italic' : undefined,
|
fontStyle: member.playerID === playerID ? 'italic' : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{member.spymaster ? `[${member.nickname}]` : member.nickname}
|
{member.spymaster ? `[${member.nickname}]` : member.nickname}
|
||||||
|
@ -500,14 +508,15 @@ const Sidebar = ({ send, state, pState, pTeam }: GameViewProps) => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isDefined(state.timer) ? null : (
|
{!isDefined(timer) ? null : (
|
||||||
<div style={{ textAlign: 'left', marginTop: '1rem' }}>
|
<div style={{ textAlign: 'left', marginTop: '1rem' }}>
|
||||||
<TimerSlider version={state.version} timer={state.timer} onCommit={send.changeTurnTime} />
|
<TimerSlider version={version} timer={timer} onCommit={send.changeTurnTime} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
isEqual);
|
||||||
|
|
||||||
const Board2 = ({ send, state, pState, pTeam }: GameViewProps) => {
|
const Board2 = ({ send, state, pState, pTeam }: GameViewProps) => {
|
||||||
const myTurn = state.turn === pTeam;
|
const myTurn = state.turn === pTeam;
|
||||||
|
@ -523,16 +532,44 @@ const Board2 = ({ send, state, pState, pTeam }: GameViewProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Footer = ({ send, state, pState }: GameViewProps) => {
|
const useFooterStyles = makeStyles((_theme: Theme) =>
|
||||||
const end = isDefined(state.winner);
|
createStyles({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignContent: 'flex-start',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
display: 'flex',
|
||||||
|
alignContent: 'flex-start',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
leftButton: {
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
marginRight: '0.5rem',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
interface FooterProps {
|
||||||
|
send: Sender;
|
||||||
|
end: boolean;
|
||||||
|
spymaster: boolean;
|
||||||
|
hideBomb: boolean;
|
||||||
|
hasTimer: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Footer = React.memo(function Footer({ send, end, spymaster, hideBomb, hasTimer }: FooterProps) {
|
||||||
|
const classes = useFooterStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignContent: 'flex-start', flexWrap: 'wrap' }}>
|
<div className={classes.root}>
|
||||||
<div style={{ display: 'flex', alignContent: 'flex-start', flexWrap: 'wrap' }}>
|
<div className={classes.left}>
|
||||||
<ButtonGroup variant="outlined" style={{ marginBottom: '0.5rem', marginRight: '0.5rem' }}>
|
<ButtonGroup variant="outlined" className={classes.leftButton}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={pState.spymaster ? undefined : 'contained'}
|
variant={spymaster ? undefined : 'contained'}
|
||||||
onClick={() => send.changeRole(false)}
|
onClick={() => send.changeRole(false)}
|
||||||
startIcon={<Search />}
|
startIcon={<Search />}
|
||||||
disabled={end}
|
disabled={end}
|
||||||
|
@ -541,7 +578,7 @@ const Footer = ({ send, state, pState }: GameViewProps) => {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={pState.spymaster ? 'contained' : undefined}
|
variant={spymaster ? 'contained' : undefined}
|
||||||
onClick={() => send.changeRole(true)}
|
onClick={() => send.changeRole(true)}
|
||||||
startIcon={<Person />}
|
startIcon={<Person />}
|
||||||
disabled={end}
|
disabled={end}
|
||||||
|
@ -549,10 +586,10 @@ const Footer = ({ send, state, pState }: GameViewProps) => {
|
||||||
Spymaster
|
Spymaster
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup variant="outlined" style={{ marginBottom: '0.5rem', marginRight: '0.5rem' }}>
|
<ButtonGroup variant="outlined" className={classes.leftButton}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={state.hideBomb ? undefined : 'contained'}
|
variant={hideBomb ? undefined : 'contained'}
|
||||||
onClick={() => send.changeHideBomb(false)}
|
onClick={() => send.changeHideBomb(false)}
|
||||||
startIcon={<Visibility />}
|
startIcon={<Visibility />}
|
||||||
>
|
>
|
||||||
|
@ -560,24 +597,24 @@ const Footer = ({ send, state, pState }: GameViewProps) => {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={state.hideBomb ? 'contained' : undefined}
|
variant={hideBomb ? 'contained' : undefined}
|
||||||
onClick={() => send.changeHideBomb(true)}
|
onClick={() => send.changeHideBomb(true)}
|
||||||
startIcon={<VisibilityOff />}
|
startIcon={<VisibilityOff />}
|
||||||
>
|
>
|
||||||
Hide bomb
|
Hide bomb
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup variant="outlined" style={{ marginBottom: '0.5rem', marginRight: '0.5rem' }}>
|
<ButtonGroup variant="outlined" className={classes.leftButton}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={isDefined(state.timer) ? undefined : 'contained'}
|
variant={hasTimer ? undefined : 'contained'}
|
||||||
onClick={() => send.changeTurnMode(false)}
|
onClick={() => send.changeTurnMode(false)}
|
||||||
>
|
>
|
||||||
<TimerOff />
|
<TimerOff />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={isDefined(state.timer) ? 'contained' : undefined}
|
variant={hasTimer ? 'contained' : undefined}
|
||||||
onClick={() => send.changeTurnMode(true)}
|
onClick={() => send.changeTurnMode(true)}
|
||||||
>
|
>
|
||||||
<Timer />
|
<Timer />
|
||||||
|
@ -597,7 +634,7 @@ const Footer = ({ send, state, pState }: GameViewProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}, isEqual);
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
|
@ -650,8 +687,18 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const GameView = (props: GameViewProps) => {
|
export interface GameViewProps {
|
||||||
|
roomID: string;
|
||||||
|
leave: () => void;
|
||||||
|
send: Sender;
|
||||||
|
state: State;
|
||||||
|
pState: StatePlayer;
|
||||||
|
pTeam: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GameView = (props: DeepReadonly<GameViewProps>) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const end = isDefined(props.state.winner);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
|
@ -673,10 +720,24 @@ export const GameView = (props: GameViewProps) => {
|
||||||
<Board2 {...props} />
|
<Board2 {...props} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.footer}>
|
<div className={classes.footer}>
|
||||||
<Footer {...props} />
|
<Footer
|
||||||
|
send={props.send}
|
||||||
|
end={end}
|
||||||
|
spymaster={props.pState.spymaster}
|
||||||
|
hideBomb={props.state.hideBomb}
|
||||||
|
hasTimer={isDefined(props.state.timer)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.sidebar}>
|
<div className={classes.sidebar}>
|
||||||
<Sidebar {...props} />
|
<Sidebar
|
||||||
|
send={props.send}
|
||||||
|
teams={props.state.teams}
|
||||||
|
lists={props.state.lists}
|
||||||
|
pTeam={props.pTeam}
|
||||||
|
playerID={props.pState.playerID}
|
||||||
|
version={props.state.version}
|
||||||
|
timer={props.state.timer}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -123,6 +123,14 @@ const StateTimer = myzod.object({
|
||||||
turnEnd: myzod.date(),
|
turnEnd: myzod.date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type StateWordList = DeepReadonly<Infer<typeof StateWordList>>;
|
||||||
|
const StateWordList = myzod.object({
|
||||||
|
name: myzod.string(),
|
||||||
|
count: myzod.number(),
|
||||||
|
custom: myzod.boolean(),
|
||||||
|
enabled: myzod.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
export type State = DeepReadonly<Infer<typeof State>>;
|
export type State = DeepReadonly<Infer<typeof State>>;
|
||||||
export const State = myzod.object({
|
export const State = myzod.object({
|
||||||
version: myzod.number(),
|
version: myzod.number(),
|
||||||
|
@ -131,14 +139,7 @@ export const State = myzod.object({
|
||||||
winner: myzod.number().optional().nullable(),
|
winner: myzod.number().optional().nullable(),
|
||||||
board: StateBoard,
|
board: StateBoard,
|
||||||
wordsLeft: myzod.array(myzod.number()),
|
wordsLeft: myzod.array(myzod.number()),
|
||||||
lists: myzod.array(
|
lists: myzod.array(StateWordList),
|
||||||
myzod.object({
|
|
||||||
name: myzod.string(),
|
|
||||||
count: myzod.number(),
|
|
||||||
custom: myzod.boolean(),
|
|
||||||
enabled: myzod.boolean(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
timer: StateTimer.optional().nullable(),
|
timer: StateTimer.optional().nullable(),
|
||||||
hideBomb: myzod.boolean(),
|
hideBomb: myzod.boolean(),
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue