Optimize the remaining rerenders

This commit is contained in:
zikaeroh 2020-05-28 20:34:11 -07:00
parent 2eaf382b67
commit 75c0694c03
2 changed files with 160 additions and 95 deletions

View File

@ -1,6 +1,7 @@
import { Button, Tooltip } from '@material-ui/core'; import { Button, Tooltip } from '@material-ui/core';
import copy from 'clipboard-copy'; import copy from 'clipboard-copy';
import * as React from 'react'; import * as React from 'react';
import isEqual from 'react-fast-compare';
export interface ClipboardButtonProps { export interface ClipboardButtonProps {
buttonText: string; buttonText: string;
@ -8,7 +9,7 @@ export interface ClipboardButtonProps {
icon: React.ReactNode; icon: React.ReactNode;
} }
export const ClipboardButton = (props: ClipboardButtonProps) => { export const ClipboardButton = React.memo(function ClipboardButton(props: ClipboardButtonProps) {
const [showTooltip, setShowTooltip] = React.useState(false); const [showTooltip, setShowTooltip] = React.useState(false);
return ( return (
@ -30,4 +31,4 @@ export const ClipboardButton = (props: ClipboardButtonProps) => {
</Button> </Button>
</Tooltip> </Tooltip>
); );
}; }, isEqual);

View File

@ -73,7 +73,13 @@ const useCenterStyles = makeStyles((_theme: Theme) =>
}) })
); );
const CenterText = ({ winner, timer, turn }: State) => { interface CenterTextProps {
winner: number | undefined | null;
timer: StateTimer | undefined | null;
turn: number;
}
const CenterText = ({ winner, timer, turn }: DeepReadonly<CenterTextProps>) => {
const classes = useCenterStyles(); const classes = useCenterStyles();
const [countdown, setCountdown] = React.useState<number | undefined>(); const [countdown, setCountdown] = React.useState<number | undefined>();
const { now } = useServerTime(); const { now } = useServerTime();
@ -135,21 +141,37 @@ const CenterText = ({ winner, timer, turn }: State) => {
); );
}; };
const Header = ({ send, state, pState, pTeam }: GameViewProps) => { interface HeaderProps {
const myTurn = state.turn === pTeam; send: Sender;
myTurn: boolean;
winner: number | undefined | null;
spymaster: boolean;
turn: number;
wordsLeft: number[];
timer: StateTimer | undefined | null;
}
const Header = React.memo(function Header({
send,
myTurn,
winner,
spymaster,
turn,
wordsLeft,
timer,
}: DeepReadonly<HeaderProps>) {
return ( return (
<Grid container direction="row" justify="space-between" alignItems="center" spacing={2}> <Grid container direction="row" justify="space-between" alignItems="center" spacing={2}>
<Grid item xs style={{ textAlign: 'left' }}> <Grid item xs style={{ textAlign: 'left' }}>
<h1> <h1>
{state.wordsLeft.map((n, team) => { {wordsLeft.map((n, team) => {
return ( return (
<span key={team}> <span key={team}>
{team !== 0 ? <span> - </span> : null} {team !== 0 ? <span> - </span> : null}
<span <span
style={{ style={{
color: teamSpecs[team].hue[600], color: teamSpecs[team].hue[600],
fontWeight: state.turn === team ? 'bold' : undefined, fontWeight: turn === team ? 'bold' : undefined,
}} }}
> >
{n} {n}
@ -160,21 +182,22 @@ const Header = ({ send, state, pState, pTeam }: GameViewProps) => {
</h1> </h1>
</Grid> </Grid>
<Grid item xs style={{ textAlign: 'center' }}> <Grid item xs style={{ textAlign: 'center' }}>
<CenterText {...state} /> <CenterText winner={winner} timer={timer} turn={turn} />
</Grid> </Grid>
<Grid item xs style={{ textAlign: 'right' }}> <Grid item xs style={{ textAlign: 'right' }}>
<Button <Button
type="button" type="button"
variant="outlined" variant="outlined"
onClick={() => myTurn && !pState.spymaster && send.endTurn()} onClick={() => myTurn && !spymaster && send.endTurn()}
disabled={!myTurn || pState.spymaster || isDefined(state.winner)} disabled={!myTurn || spymaster || isDefined(winner)}
> >
End turn End turn
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
); );
}; },
isEqual);
const sliderMarks = range(30, 301, 30).map((v) => ({ value: v })); const sliderMarks = range(30, 301, 30).map((v) => ({ value: v }));
@ -333,53 +356,22 @@ const ChangeNicknameButton = ({ send }: { send: Sender }) => {
); );
}; };
const useSidebarStyles = makeStyles((_theme: Theme) => interface SidebarTeamsProps {
createStyles({
dropzone: {
backgroundColor: 'initial',
},
previewGrid: {
width: '100%',
},
})
);
interface SidebarProps {
send: Sender; send: Sender;
teams: StateTeams; teams: StateTeams;
lists: StateWordList[];
pTeam: number; pTeam: number;
playerID: string; playerID: string;
version: number;
timer: StateTimer | undefined | null;
} }
const Sidebar = React.memo(function Sidebar({ const SidebarTeams = React.memo(function SidebarTeams({
send, send,
teams, teams,
lists,
pTeam, pTeam,
playerID, playerID,
version, }: DeepReadonly<SidebarTeamsProps>) {
timer,
}: DeepReadonly<SidebarProps>) {
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 wordCount = React.useMemo(
() =>
lists.reduce((curr, l) => {
if (l.enabled) {
return curr + l.count;
}
return curr;
}, 0),
[lists]
);
const [uploadOpen, setUploadOpen] = React.useState(false);
return ( return (
<> <>
<h2>Teams</h2> <h2>Teams</h2>
@ -436,7 +428,45 @@ const Sidebar = React.memo(function Sidebar({
</Button> </Button>
<ChangeNicknameButton send={send} /> <ChangeNicknameButton send={send} />
</Paper> </Paper>
</>
);
},
isEqual);
const useSidebarPacksStyles = makeStyles((_theme: Theme) =>
createStyles({
dropzone: {
backgroundColor: 'initial',
},
previewGrid: {
width: '100%',
},
})
);
interface SidebarPacksProps {
send: Sender;
lists: StateWordList[];
}
const SidebarPacks = React.memo(function SidebarPacks({ send, lists }: DeepReadonly<SidebarPacksProps>) {
const classes = useSidebarPacksStyles();
const wordCount = React.useMemo(
() =>
lists.reduce((curr, l) => {
if (l.enabled) {
return curr + l.count;
}
return curr;
}, 0),
[lists]
);
const [uploadOpen, setUploadOpen] = React.useState(false);
return (
<>
<h2>Packs</h2> <h2>Packs</h2>
<p style={{ fontStyle: 'italic' }}>{wordCount} words in the selected packs.</p> <p style={{ fontStyle: 'italic' }}>{wordCount} words in the selected packs.</p>
<div style={{ display: 'grid', gridGap: '0.5rem' }}> <div style={{ display: 'grid', gridGap: '0.5rem' }}>
@ -508,6 +538,25 @@ const Sidebar = React.memo(function Sidebar({
</> </>
)} )}
</div> </div>
</>
);
}, isEqual);
interface SidebarProps {
send: Sender;
teams: StateTeams;
lists: StateWordList[];
pTeam: number;
playerID: string;
version: number;
timer: StateTimer | undefined | null;
}
const Sidebar = ({ send, teams, lists, pTeam, playerID, version, timer }: DeepReadonly<SidebarProps>) => {
return (
<>
<SidebarTeams send={send} teams={teams} pTeam={pTeam} playerID={playerID} />
<SidebarPacks send={send} lists={lists} />
{!isDefined(timer) ? null : ( {!isDefined(timer) ? null : (
<div style={{ textAlign: 'left', marginTop: '1rem' }}> <div style={{ textAlign: 'left', marginTop: '1rem' }}>
<TimerSlider version={version} timer={timer} onCommit={send.changeTurnTime} /> <TimerSlider version={version} timer={timer} onCommit={send.changeTurnTime} />
@ -515,21 +564,6 @@ const Sidebar = React.memo(function Sidebar({
)} )}
</> </>
); );
},
isEqual);
const Board2 = ({ send, state, pState, pTeam }: GameViewProps) => {
const myTurn = state.turn === pTeam;
return (
<Board
words={state.board}
onClick={send.reveal}
spymaster={pState.spymaster}
myTurn={myTurn}
winner={isDefined(state.winner)}
/>
);
}; };
const useFooterStyles = makeStyles((_theme: Theme) => const useFooterStyles = makeStyles((_theme: Theme) =>
@ -560,7 +594,7 @@ interface FooterProps {
hasTimer: boolean; hasTimer: boolean;
} }
const Footer = React.memo(function Footer({ send, end, spymaster, hideBomb, hasTimer }: FooterProps) { const Footer = React.memo(function Footer({ send, end, spymaster, hideBomb, hasTimer }: DeepReadonly<FooterProps>) {
const classes = useFooterStyles(); const classes = useFooterStyles();
return ( return (
@ -636,6 +670,39 @@ const Footer = React.memo(function Footer({ send, end, spymaster, hideBomb, hasT
); );
}, isEqual); }, isEqual);
const useCornerButtonsStyle = makeStyles((_theme: Theme) =>
createStyles({
wrapper: {
position: 'absolute',
top: 0,
left: 0,
margin: '0.5rem',
},
button: {
marginRight: '0.5rem',
},
})
);
const CornerButtons = React.memo(function CornerButtons({ roomID, leave }: { roomID: string; leave: () => void }) {
const classes = useCornerButtonsStyle();
return (
<>
<div className={classes.wrapper}>
<Button type="button" onClick={leave} startIcon={<ArrowBack />} className={classes.button}>
Leave
</Button>
<ClipboardButton
buttonText="Copy Room URL"
toCopy={`${window.location.origin}/?roomID=${roomID}`}
icon={<Link />}
/>
</div>
</>
);
});
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
root: { root: {
@ -675,15 +742,6 @@ const useStyles = makeStyles((theme: Theme) =>
sidebar: { sidebar: {
gridArea: 'sidebar', gridArea: 'sidebar',
}, },
leaveWrapper: {
position: 'absolute',
top: 0,
left: 0,
margin: '0.5rem',
},
leaveButton: {
marginRight: '0.5rem',
},
}) })
); );
@ -696,47 +754,53 @@ export interface GameViewProps {
pTeam: number; pTeam: number;
} }
export const GameView = (props: DeepReadonly<GameViewProps>) => { export const GameView = ({ roomID, leave, send, state, pState, pTeam }: DeepReadonly<GameViewProps>) => {
const classes = useStyles(); const classes = useStyles();
const end = isDefined(props.state.winner); const end = isDefined(state.winner);
const myTurn = state.turn === pTeam;
return ( return (
<div className={classes.root}> <div className={classes.root}>
<div className={classes.leaveWrapper}> <CornerButtons roomID={roomID} leave={leave} />
<Button type="button" onClick={props.leave} startIcon={<ArrowBack />} className={classes.leaveButton}>
Leave
</Button>
<ClipboardButton
buttonText="Copy Room URL"
toCopy={`${window.location.origin}/?roomID=${props.roomID}`}
icon={<Link />}
/>
</div>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.header}> <div className={classes.header}>
<Header {...props} /> <Header
send={send}
myTurn={myTurn}
winner={state.winner}
spymaster={pState.spymaster}
turn={state.turn}
wordsLeft={state.wordsLeft}
timer={state.timer}
/>
</div> </div>
<div className={classes.board}> <div className={classes.board}>
<Board2 {...props} /> <Board
words={state.board}
onClick={send.reveal}
spymaster={pState.spymaster}
myTurn={myTurn}
winner={end}
/>
</div> </div>
<div className={classes.footer}> <div className={classes.footer}>
<Footer <Footer
send={props.send} send={send}
end={end} end={end}
spymaster={props.pState.spymaster} spymaster={pState.spymaster}
hideBomb={props.state.hideBomb} hideBomb={state.hideBomb}
hasTimer={isDefined(props.state.timer)} hasTimer={isDefined(state.timer)}
/> />
</div> </div>
<div className={classes.sidebar}> <div className={classes.sidebar}>
<Sidebar <Sidebar
send={props.send} send={send}
teams={props.state.teams} teams={state.teams}
lists={props.state.lists} lists={state.lists}
pTeam={props.pTeam} pTeam={pTeam}
playerID={props.pState.playerID} playerID={pState.playerID}
version={props.state.version} version={state.version}
timer={props.state.timer} timer={state.timer}
/> />
</div> </div>
</div> </div>