Optimize a few more components

This commit is contained in:
zikaeroh 2020-05-28 19:55:12 -07:00
parent 1bf7a62c82
commit 2eaf382b67
2 changed files with 105 additions and 43 deletions

View File

@ -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>

View File

@ -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(),
}); });