Optimize the remaining rerenders
This commit is contained in:
parent
2eaf382b67
commit
75c0694c03
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue