Change the timer data structure to fix network bugs and refactored shared timer functions

This commit is contained in:
Mitchell McCaffrey 2020-08-05 12:01:54 +10:00
parent 0c1ec22234
commit 4199d7ab6a
5 changed files with 83 additions and 51 deletions

View File

@ -58,7 +58,15 @@ function Party({
stream={partyStreams[id]} stream={partyStreams[id]}
/> />
))} ))}
<Timer timer={timer} /> {timer && <Timer timer={timer} index={0} />}
{Object.entries(partyTimers).map(([id, partyTimer], index) => (
<Timer
timer={partyTimer}
key={id}
// Put party timers above your timer if there is one
index={timer ? index + 1 : index}
/>
))}
</Box> </Box>
<Flex sx={{ flexDirection: "column" }}> <Flex sx={{ flexDirection: "column" }}>
<ChangeNicknameButton nickname={nickname} onChange={onNicknameChange} /> <ChangeNicknameButton nickname={nickname} onChange={onNicknameChange} />

View File

@ -1,33 +1,18 @@
import React, { useEffect, useState, useRef } from "react"; import React, { useEffect, useRef } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { Box, Progress } from "theme-ui"; import { Box, Progress } from "theme-ui";
import usePortal from "../../helpers/usePortal"; import usePortal from "../../helpers/usePortal";
function getTimerDuration(t) { function Timer({ timer, index }) {
if (!t) {
return 0;
}
return t.hour * 3600000 + t.minute * 60000 + t.second * 1000;
}
function Timer({ timer }) {
const [maxDuration, setMaxDuration] = useState(0);
const previousTimerRef = useRef(timer);
useEffect(() => {
if (!previousTimerRef.current && timer) {
setMaxDuration(getTimerDuration(timer));
}
previousTimerRef.current = timer;
});
useEffect(() => {
progressBarRef.current.value = getTimerDuration(timer);
}, [timer]);
const progressBarRef = useRef(); const progressBarRef = useRef();
useEffect(() => {
if (progressBarRef.current && timer) {
progressBarRef.current.value = timer.current;
}
}, [timer]);
useEffect(() => { useEffect(() => {
let request = requestAnimationFrame(animate); let request = requestAnimationFrame(animate);
let previousTime = performance.now(); let previousTime = performance.now();
@ -36,13 +21,15 @@ function Timer({ timer }) {
const deltaTime = time - previousTime; const deltaTime = time - previousTime;
previousTime = time; previousTime = time;
progressBarRef.current.value -= deltaTime; if (progressBarRef.current && progressBarRef.current.value > 0) {
progressBarRef.current.value -= deltaTime;
}
} }
return () => { return () => {
cancelAnimationFrame(request); cancelAnimationFrame(request);
}; };
}, [maxDuration]); }, []);
const timerContainer = usePortal("root"); const timerContainer = usePortal("root");
@ -59,15 +46,15 @@ function Timer({ timer }) {
borderRadius: "28px", borderRadius: "28px",
left: "50%", left: "50%",
maxWidth: "500px", maxWidth: "500px",
width: "60%", width: "40%",
transform: "translateX(-50%)", transform: `translate(-50%, -${index * 36}px)`,
padding: "0 8px", padding: "0 8px",
margin: "8px", margin: "8px",
}} }}
bg="overlay" bg="overlay"
> >
<Progress <Progress
max={maxDuration} max={timer && timer.max}
m={2} m={2}
sx={{ width: "100%" }} sx={{ width: "100%" }}
ref={progressBarRef} ref={progressBarRef}

33
src/helpers/timer.js Normal file
View File

@ -0,0 +1,33 @@
const MILLISECONDS_IN_HOUR = 3600000;
const MILLISECONDS_IN_MINUTE = 60000;
const MILLISECONDS_IN_SECOND = 1000;
/**
* Returns a timers duration in milliseconds
* @param {Object} t The object with an hour, minute and second property
*/
export function getHMSDuration(t) {
if (!t) {
return 0;
}
return (
t.hour * MILLISECONDS_IN_HOUR +
t.minute * MILLISECONDS_IN_MINUTE +
t.second * MILLISECONDS_IN_SECOND
);
}
/**
* Returns an object with an hour, minute and second property
* @param {number} duration The duration in milliseconds
*/
export function getDurationHMS(duration) {
let workingDuration = duration;
const hour = Math.floor(workingDuration / MILLISECONDS_IN_HOUR);
workingDuration -= hour * MILLISECONDS_IN_HOUR;
const minute = Math.floor(workingDuration / MILLISECONDS_IN_MINUTE);
workingDuration -= minute * MILLISECONDS_IN_MINUTE;
const second = Math.floor(workingDuration / MILLISECONDS_IN_SECOND);
return { hour, minute, second };
}

View File

@ -3,6 +3,8 @@ import { Box, Label, Input, Button, Flex, Text } from "theme-ui";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import { getHMSDuration, getDurationHMS } from "../helpers/timer";
function StartTimerModal({ function StartTimerModal({
isOpen, isOpen,
onRequestClose, onRequestClose,
@ -24,7 +26,8 @@ function StartTimerModal({
if (timer) { if (timer) {
onTimerStop(); onTimerStop();
} else { } else {
onTimerStart({ hour, minute, second }); const duration = getHMSDuration({ hour, minute, second });
onTimerStart({ current: duration, max: duration });
} }
} }
@ -47,6 +50,8 @@ function StartTimerModal({
return Math.min(num, max); return Math.min(num, max);
} }
const timerHMS = timer && getDurationHMS(timer.current);
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
@ -70,7 +75,7 @@ function StartTimerModal({
</Text> </Text>
<Input <Input
sx={inputStyle} sx={inputStyle}
value={`${timer ? timer.hour : hour}`} value={`${timer ? timerHMS.hour : hour}`}
onChange={(e) => setHour(parseValue(e.target.value, 24))} onChange={(e) => setHour(parseValue(e.target.value, 24))}
type="number" type="number"
disabled={timer} disabled={timer}
@ -82,7 +87,7 @@ function StartTimerModal({
</Text> </Text>
<Input <Input
sx={inputStyle} sx={inputStyle}
value={`${timer ? timer.minute : minute}`} value={`${timer ? timerHMS.minute : minute}`}
onChange={(e) => setMinute(parseValue(e.target.value, 59))} onChange={(e) => setMinute(parseValue(e.target.value, 59))}
type="number" type="number"
ref={inputRef} ref={inputRef}
@ -95,7 +100,7 @@ function StartTimerModal({
</Text> </Text>
<Input <Input
sx={inputStyle} sx={inputStyle}
value={`${timer ? timer.second : second}`} value={`${timer ? timerHMS.second : second}`}
onChange={(e) => setSecond(parseValue(e.target.value, 59))} onChange={(e) => setSecond(parseValue(e.target.value, 59))}
type="number" type="number"
disabled={timer} disabled={timer}

View File

@ -3,7 +3,7 @@ import React, { useContext, useState, useEffect, useCallback } from "react";
// Load session for auto complete // Load session for auto complete
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import Session from "../helpers/Session"; import Session from "../helpers/Session";
import { isStreamStopped, omit } from "../helpers/shared"; import { isStreamStopped, omit, fromEntries } from "../helpers/shared";
import AuthContext from "../contexts/AuthContext"; import AuthContext from "../contexts/AuthContext";
@ -73,23 +73,18 @@ function NetworkedParty({ gameId, session }) {
useEffect(() => { useEffect(() => {
function decreaseTimer(previousTimer) { function decreaseTimer(previousTimer) {
if (previousTimer.second > 0) { return { ...previousTimer, current: previousTimer.current - 1000 };
return { ...previousTimer, second: previousTimer.second - 1 };
} else if (previousTimer.minute > 0) {
return {
...previousTimer,
minute: previousTimer.minute - 1,
second: 59,
};
} else if (previousTimer.hour > 0) {
return { hour: previousTimer.hour - 1, minute: 59, second: 59 };
} else return { hour: 0, minute: 0, second: 0 };
} }
function updateTimers() { function updateTimers() {
if (timer) { if (timer) {
const newTimer = decreaseTimer(timer); const newTimer = decreaseTimer(timer);
setTimer(newTimer); if (newTimer.current < 0) {
session.send("timer", { [session.id]: newTimer }); setTimer(null);
session.send("timer", { [session.id]: null });
} else {
setTimer(newTimer);
session.send("timer", { [session.id]: newTimer });
}
} }
} }
const interval = setInterval(updateTimers, 1000); const interval = setInterval(updateTimers, 1000);
@ -122,10 +117,14 @@ function NetworkedParty({ gameId, session }) {
})); }));
} }
if (id === "timer") { if (id === "timer") {
setPartyTimers((prevTimers) => ({ setPartyTimers((prevTimers) => {
...prevTimers, const newTimers = { ...prevTimers, ...data };
...data, // filter out timers that are null
})); const filtered = Object.entries(newTimers).filter(
([, value]) => value !== null
);
return fromEntries(filtered);
});
} }
} }