Event driven, add clue clock
This commit is contained in:
parent
c1779907d7
commit
de65fddba2
@ -1,19 +1,20 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
import { debounce } from "./utils";
|
import { debounce } from "./utils";
|
||||||
import { socket } from "./socket";
|
import { socket } from "./socket";
|
||||||
import { useAppSelector } from "./hooks";
|
import { useAppSelector } from "./hooks";
|
||||||
import { selectCanBuzz, selectSignature } from "./store/contestantSlice";
|
import { selectSignature } from "./store/contestantSlice";
|
||||||
|
|
||||||
const Contestant = () => {
|
const Contestant = () => {
|
||||||
const { room } = useParams();
|
const { room } = useParams();
|
||||||
|
|
||||||
const signature = useAppSelector(selectSignature);
|
const signature = useAppSelector(selectSignature);
|
||||||
const canBuzz = useAppSelector(selectCanBuzz);
|
const [canBuzz, setCanBuzz] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.emit("contestant-join", { room, signature });
|
socket.emit("contestant-join", { room, signature });
|
||||||
|
socket.on("clue-clock-on", () => setCanBuzz(true));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleBuzz = debounce(() => {
|
const handleBuzz = debounce(() => {
|
||||||
|
48
src/Timer.tsx
Normal file
48
src/Timer.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { clamp } from "lodash";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
startTime: number | null;
|
||||||
|
length: number;
|
||||||
|
onTimeout?: () => unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const invLerp = (a: number, b: number, x: number): number =>
|
||||||
|
clamp((x - b) / (a - b), 0, 1);
|
||||||
|
|
||||||
|
const Timer = ({ startTime, length, onTimeout }: Props) => {
|
||||||
|
const [segs, setSegs] = useState<number>(0);
|
||||||
|
const updateTimer = () => {
|
||||||
|
if (!startTime) return;
|
||||||
|
const endTime = startTime + length;
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const segs = invLerp(startTime, endTime, currentTime) * 5;
|
||||||
|
setSegs(segs);
|
||||||
|
if (segs > 0) {
|
||||||
|
setTimeout(updateTimer, 100);
|
||||||
|
} else if (onTimeout) {
|
||||||
|
onTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(updateTimer, 100);
|
||||||
|
}, [startTime, length]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 90 50" style={{ width: "100%", height: "100%" }}>
|
||||||
|
{[5, 4, 3, 2, 1, 2, 3, 4, 5].map((seg, i) => (
|
||||||
|
<rect
|
||||||
|
x={i * 10}
|
||||||
|
y="0"
|
||||||
|
width="10"
|
||||||
|
height="5"
|
||||||
|
key={i}
|
||||||
|
fill={seg <= segs ? "yellow" : "black"}
|
||||||
|
stroke="blue"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Timer;
|
@ -1,11 +1,24 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import Timer from "../Timer";
|
||||||
import type { Clue } from "../store/cluesSlice";
|
import type { Clue } from "../store/cluesSlice";
|
||||||
|
import { socket } from "../socket";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeClue: Clue;
|
activeClue: Clue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClueDisplay = ({ activeClue }: Props) => {
|
const ClueDisplay = ({ activeClue }: Props) => {
|
||||||
return <div className="text-center fs-1">{activeClue.question}</div>;
|
const [startTime, setStartTime] = useState<number | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
socket.on("clue-clock-on", () => setStartTime(Date.now()));
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="text-center fs-1">{activeClue.question}</div>
|
||||||
|
<Timer startTime={startTime} length={5000} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ClueDisplay;
|
export default ClueDisplay;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import { setRoomCode, setShouldStartClueClock } from "./store/commonSlice";
|
import { setRoomCode } from "./store/commonSlice";
|
||||||
import { addContestant } from "./store/displaySlice";
|
import { addContestant } from "./store/displaySlice";
|
||||||
import { setCanBuzz } from "./store/contestantSlice";
|
|
||||||
import { setCategories, setActiveClue } from "./store/cluesSlice";
|
import { setCategories, setActiveClue } from "./store/cluesSlice";
|
||||||
import type { Clue } from "./store/cluesSlice";
|
import type { Clue } from "./store/cluesSlice";
|
||||||
|
|
||||||
@ -31,9 +30,4 @@ export const setup = () => {
|
|||||||
socket.on("active-clue", (data: Clue) => {
|
socket.on("active-clue", (data: Clue) => {
|
||||||
store.dispatch(setActiveClue(data));
|
store.dispatch(setActiveClue(data));
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("clue-clock-on", () => {
|
|
||||||
store.dispatch(setCanBuzz(true));
|
|
||||||
store.dispatch(setShouldStartClueClock(true));
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -3,12 +3,10 @@ import type { RootState } from "./";
|
|||||||
|
|
||||||
interface CommonState {
|
interface CommonState {
|
||||||
roomCode: string;
|
roomCode: string;
|
||||||
shouldStartClueClock: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: CommonState = {
|
const initialState: CommonState = {
|
||||||
roomCode: "",
|
roomCode: "",
|
||||||
shouldStartClueClock: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const commonSlice = createSlice({
|
export const commonSlice = createSlice({
|
||||||
@ -18,15 +16,10 @@ export const commonSlice = createSlice({
|
|||||||
setRoomCode: (state, { payload }: PayloadAction<string>) => {
|
setRoomCode: (state, { payload }: PayloadAction<string>) => {
|
||||||
state.roomCode = payload;
|
state.roomCode = payload;
|
||||||
},
|
},
|
||||||
setShouldStartClueClock: (state, { payload }: PayloadAction<boolean>) => {
|
|
||||||
state.shouldStartClueClock = payload;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setRoomCode, setShouldStartClueClock } = commonSlice.actions;
|
export const { setRoomCode } = commonSlice.actions;
|
||||||
export const selectRoomCode = (state: RootState) => state.common.roomCode;
|
export const selectRoomCode = (state: RootState) => state.common.roomCode;
|
||||||
export const selectShouldStartClueClock = (state: RootState) =>
|
|
||||||
state.common.shouldStartClueClock;
|
|
||||||
|
|
||||||
export default commonSlice.reducer;
|
export default commonSlice.reducer;
|
||||||
|
@ -3,12 +3,10 @@ import type { RootState } from "./";
|
|||||||
|
|
||||||
interface ContestantState {
|
interface ContestantState {
|
||||||
signature: number[][];
|
signature: number[][];
|
||||||
canBuzz: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ContestantState = {
|
const initialState: ContestantState = {
|
||||||
signature: [],
|
signature: [],
|
||||||
canBuzz: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const contestantSlice = createSlice({
|
export const contestantSlice = createSlice({
|
||||||
@ -18,14 +16,10 @@ export const contestantSlice = createSlice({
|
|||||||
addPathToSignature: (state, { payload }: PayloadAction<number[]>) => {
|
addPathToSignature: (state, { payload }: PayloadAction<number[]>) => {
|
||||||
state.signature = [...state.signature, payload];
|
state.signature = [...state.signature, payload];
|
||||||
},
|
},
|
||||||
setCanBuzz: (state, { payload }: PayloadAction<boolean>) => {
|
|
||||||
state.canBuzz = payload;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { addPathToSignature, setCanBuzz } = contestantSlice.actions;
|
export const { addPathToSignature } = contestantSlice.actions;
|
||||||
export const selectSignature = (state: RootState) => state.contestant.signature;
|
export const selectSignature = (state: RootState) => state.contestant.signature;
|
||||||
export const selectCanBuzz = (state: RootState) => state.contestant.canBuzz;
|
|
||||||
|
|
||||||
export default contestantSlice.reducer;
|
export default contestantSlice.reducer;
|
||||||
|
Loading…
Reference in New Issue
Block a user