Skip to content

Commit

Permalink
Merge branch 'master' of github.com:joshzcold/Cold-Family-Feud into d…
Browse files Browse the repository at this point in the history
…evelop
  • Loading branch information
joshzcold committed Dec 29, 2024
2 parents e30b66a + 85e956d commit 20905be
Show file tree
Hide file tree
Showing 23 changed files with 783 additions and 242 deletions.
43 changes: 32 additions & 11 deletions components/Admin/csv-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next";
import "i18n/i18n";
import "tailwindcss/tailwind.css";
import { useState, useEffect, useRef } from "react";
import { ERROR_CODES } from "i18n/errorCodes";

export function csvStringToArray(data) {
const re = /(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))/gi;
Expand Down Expand Up @@ -30,7 +31,7 @@ function validateCsv(
setError,
t
) {
let headerOffSet = noHeader ? 1 : 0;
let headerOffSet = noHeader ? 0 : 1;
// the setting of rows is greater than the data provided
if (roundCount + roundFinalCount + headerOffSet > csvData.length) {
setError(
Expand Down Expand Up @@ -59,11 +60,9 @@ function validateCsv(
continue colLoop;
} else {
// Answer point needs to be a number
if (!/^\d+$/.test(csvData[index][i])) {
if (csvData[index][i] && !/^\d+$/.test(csvData[index][i])) {
setError(
t(
"Error: csv file needs expected format: Question, Answer, Points (number), Answer, Points (number) ..."
)
t(ERROR_CODES.CSV_INVALID_FORMAT)
);
return;
}
Expand All @@ -83,7 +82,7 @@ function csvToColdFriendlyFeudFormat(
setError,
send
) {
let headerOffSet = noHeader ? 1 : 0;
let headerOffSet = noHeader ? 0 : 1;
let gameTemplate = {
settings: {},
rounds: [
Expand Down Expand Up @@ -179,22 +178,43 @@ export default function CSVLoader(props) {
<hr />
</div>
{error ? (
<div id="csvErrorText" className="p-4 bg-failure-200 rounded">
<p>{error}</p>
<div id="csvErrorText" className="p-4 bg-failure-200 rounded space-y-2">
<p className="font-bold">{error}</p>
<div className="text-sm space-y-1">
<p>{t("Expected format")}:</p>
<p>{t("Format Example")}</p>
<p>{t("Points must be numbers")}</p>
<p>{t("Example row")}: What is popular?, Pizza, 30, Burger, 25, Fries, 20</p>
</div>
</div>
) : null}
<div className="bg-secondary-300 p-4 rounded space-y-2">
<p className="font-semibold">{t("Format Guide")}:</p>
<div className="text-sm space-y-1">
<div className="grid grid-cols-7 gap-2">
<div className="bg-secondary-500 p-2 rounded">{t("Question")}</div>
<div className="bg-success-200 p-2 rounded">{t("Answer")} 1</div>
<div className="bg-primary-200 p-2 rounded">{t("points")} 1</div>
<div className="bg-success-200 p-2 rounded">{t("Answer")} 2</div>
<div className="bg-primary-200 p-2 rounded">{t("points")} 2</div>
<div className="bg-success-200 p-2 rounded">...</div>
<div className="bg-primary-200 p-2 rounded">...</div>
</div>
<p className="text-xs mt-2">{t("Each row follows this pattern. Points must be numbers.")}</p>
</div>
</div>
<div className="p-2 flex flex-col bg-secondary-500 overflow-x-scroll h-96 ">
{csvData.map((row, roundCounter) => {
return (
<div id={`csvRow${roundCounter}`} className="grid grid-flow-col divide-dashed divide-x divide-secondary-900">
<div key={`csvloader-round-${roundCounter}`} id={`csvRow${roundCounter}`} className="grid grid-flow-col divide-dashed divide-x divide-secondary-900">
{row.map((col, colidx) => {
let rowBackgroundColor = "bg-secondary-500";
let rowTextColor = "text-foreground";
let roundOffSet = 0;
if (!noHeader) {
if (noHeader) {
roundOffSet = -1;
}
if (roundCounter === 0 && noHeader) {
if (roundCounter === 0 && !noHeader) {
rowTextColor = "text-secondary-900";
} else if (roundCounter - 1 < roundCount + roundOffSet) {
rowBackgroundColor = "bg-success-200";
Expand All @@ -210,6 +230,7 @@ export default function CSVLoader(props) {
return (
<div
id={`csvRow${roundCounter}Col${colidx}`}
key={`csvloader-round-${roundCounter}-${index}`}
className={`w-96 font-bold p-4 ${rowBackgroundColor} ${rowTextColor} border-y border-dashed border-secondary-900 `}
>
<p className="text-ellipsis whitespace-nowrap overflow-hidden">
Expand Down
2 changes: 1 addition & 1 deletion components/Admin/players.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function Players(props) {
return (
<div>
{team.map((x, i) => (
<div className="border-2 flex flex-row space-x-5 items-center m-2">
<div key={`teamSection-player-${x.id}`} className="border-2 flex flex-row space-x-5 items-center m-2">
{/* information about player */}
<div className="flex-grow">
<p className="uppercase text-foreground">{x.name}</p>
Expand Down
2 changes: 1 addition & 1 deletion components/Admin/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function ThemeSwitcher(props) {
{Object.keys(availableThemes).map((key, index) => (
<option
value={key}
key={index}
key={`theme-${index}`}
style={{
backgroundColor: availableThemes[key].bgcolor
}}
Expand Down
61 changes: 37 additions & 24 deletions components/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import LanguageSwitcher from "./language";
import CSVLoader from "./Admin/csv-loader";
import { Buffer } from "buffer";
import { BSON } from "bson";
import { ERROR_CODES } from "i18n/errorCodes";

function debounce(callback, wait = 400) {
let timeout;
Expand Down Expand Up @@ -82,7 +83,9 @@ function FinalRoundButtonControls(props) {
? props.game.final_round_2
: props.game.final_round;
return controlRound?.map((x, i) => (
<div key={`round-${i}`} className="flex-col flex space-y-5 p-12 border-2">
<div
key={`${props.game.is_final_second ? 'final-round-2' : 'final-round-1'}-question-${i}`}
className="flex-col flex space-y-5 p-12 border-2">
<p className="text-3xl font-bold text-foreground">{x.question}</p>
{props.game.is_final_second && (
<div className="flex flex-row space-x-5 pb-2">
Expand Down Expand Up @@ -238,7 +241,7 @@ function TitleLogoUpload(props) {
if (fileSize > 2098) {
console.error("Logo image is too large");
props.setError(
t("Logo image is too large. 2MB is the limit"),
t(ERROR_CODES.IMAGE_TOO_LARGE, { message: "2MB" })
);
return;
}
Expand Down Expand Up @@ -270,7 +273,7 @@ function TitleLogoUpload(props) {
mimetype = "jpeg";
break;
default:
props.setError(t("Unknown file type"));
props.setError(t(ERROR_CODES.UNKNOWN_FILE_TYPE));
return;
}

Expand Down Expand Up @@ -396,10 +399,10 @@ export default function Admin(props) {
}

useEffect(() => {
setInterval(() => {
const retryInterval = setInterval(() => {
if (ws.current.readyState !== 1) {
setError(
`lost connection to server refreshing in ${5 - refreshCounter}`,
t(ERROR_CODES.CONNECTION_LOST, {message: `${5 - refreshCounter}`}),
);
refreshCounter++;
if (refreshCounter >= 10) {
Expand All @@ -409,7 +412,12 @@ export default function Admin(props) {
}
}, 1000);

ws.current.addEventListener("message", (evt) => {
const pongInterval = setInterval(() => {
console.debug("sending pong in admin");
send({ action: "pong" });
}, 5000);

const handleMessage = (evt) => {
var received_msg = evt.data;
let json = JSON.parse(received_msg);
if (json.action === "data") {
Expand All @@ -422,17 +430,24 @@ export default function Admin(props) {
setGameSelector([]);
}
} else if (json.action === "error") {
console.error(json.message);
setError(json.message);
console.error(json.code);
setError(t(json.code, { message: json.message }));
} else if (json.action === "timer_complete") {
setTimerStarted(false);
setTimerCompleted(true);
} else {
console.debug("did not expect admin: ", json);
}
});
}

ws.current.addEventListener("message", handleMessage);
send({ action: "change_lang", data: i18n.language });
}, []);
return () => {
clearInterval(pongInterval);
clearInterval(retryInterval);
ws.current.removeEventListener("message", handleMessage);
};
}, [i18n.language]);

if (game.teams != null) {
let current_screen;
Expand Down Expand Up @@ -510,6 +525,7 @@ export default function Admin(props) {
{gameSelector.length > 0 ? (
<select
id="gameSelector"
defaultValue={""}
className="border-2 rounded bg-secondary-500 text-foreground"
onChange={(e) => {
send({
Expand All @@ -518,11 +534,10 @@ export default function Admin(props) {
lang: i18n.language,
});
}}
defaultValue="default"
>
<option disabled value="default"></option>
<option disabled value="">{t("Select question set")}</option>
{gameSelector.map((value, index) => (
<option key={index} value={value}>
<option key={`set-${index}`} value={value}>
{value.replace(".json", "")}
</option>
))}
Expand Down Expand Up @@ -557,7 +572,7 @@ export default function Admin(props) {
};
reader.onerror = function(evt) {
console.error("error reading file");
setError(t("error reading file"));
setError(t(ERROR_CODES.PARSE_ERROR));
};
}
} else if (file?.type === "text/csv") {
Expand All @@ -566,18 +581,18 @@ export default function Admin(props) {
reader.onload = function(evt) {
let lineCount = evt.target.result.split("\n");
if (lineCount.length > 30) {
setError(t("This csv file is too large"));
setError(t(ERROR_CODES.CSV_TOO_LARGE));
} else {
setCsvFileUpload(file);
setCsvFileUploadText(evt.target.result);
}
};
reader.onerror = function(evt) {
console.error("error reading file");
setError(t("error reading file"));
setError(t(ERROR_CODES.PARSE_ERROR));
};
} else {
setError(t("Unknown file type in game load"));
setError(t(ERROR_CODES.UNKNOWN_FILE_TYPE));
}
// allow same file to be selected again
document.getElementById("gamePickerFileUpload").value = null;
Expand Down Expand Up @@ -691,7 +706,7 @@ export default function Admin(props) {
</div>
</div>
<p id="errorText" className="text-xl text-failure-700">
{error}
{error.code ? t(error.code, { message: error.message }) : t(error)}
</p>
</div>
<hr className="my-12" />
Expand Down Expand Up @@ -780,7 +795,7 @@ export default function Admin(props) {
}}
>
{game.rounds.map((key, index) => (
<option value={index} key={index}>
<option value={index} key={`round-select-${index}`}>
{t("round")} {t("number", { count: index + 1 })}
</option>
))}
Expand Down Expand Up @@ -939,7 +954,7 @@ export default function Admin(props) {
<div className=" text-white rounded border-4 grid grid-rows-4 grid-flow-col p-3 mx-10 mt-5 gap-3 ">
{current_round.answers.map((x, index) => (
<div
key={index}
key={`answer-${index}`}
className={`${
x.trig ? "bg-secondary-500" : "bg-primary-700"
} font-extrabold uppercase rounded border-2 text-2xl`}
Expand Down Expand Up @@ -1023,11 +1038,9 @@ export default function Admin(props) {
<hr />
<div className="flex-grow">
{game.buzzed.map((x, i) => (
<div className="flex flex-row space-x-5 justify-center">
<p
<div key={`buzzer-${x.id}-${i}`} className="flex flex-row space-x-5 justify-center">
<p className="text-foreground">
id={`playerBuzzed${i}NameText`}
className="text-foreground"
>
{t("number", { count: i + 1 })}.{" "}
{game.registeredPlayers[x.id]?.name}
</p>
Expand Down
23 changes: 8 additions & 15 deletions components/buzzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Round from "./round";
import QuestionBoard from "./question-board.js";
import TeamName from "./team-name.js";
import Final from "./final";
import { ERROR_CODES } from "i18n/errorCodes";

let timerInterval = null;

Expand Down Expand Up @@ -41,7 +42,7 @@ export default function Buzzer(props) {
setInterval(() => {
if (ws.current.readyState !== 1) {
setError(
`lost connection to server refreshing in ${5 - refreshCounter}`,
t(ERROR_CODES.CONNECTION_LOST, {message: `${5 - refreshCounter}`}),
);
refreshCounter++;
if (refreshCounter >= 10) {
Expand Down Expand Up @@ -183,7 +184,7 @@ export default function Buzzer(props) {
<div className="flex flex-col">
{game.buzzed.map((x, i) => (
<div
key={i}
key={`buzzer-${x.id}-${i}`}
className="flex flex-row space-x-2 md:text-2xl lg:text-2xl text-1xl"
>
<div className="flex-grow">
Expand Down Expand Up @@ -277,7 +278,7 @@ export default function Buzzer(props) {
<div className="grid grid-cols-2 gap-4">
<button
id="joinTeam1"
className="hover:shadow-md rounded-md bg-primary-200 p-5"
className={`hover:shadow-md rounded-md bg-primary-200 p-5 ${props.team === 0 ? 'border-2 border-sky-600' : ''}`}
onClick={() => {
props.setTeam(0);
}}
Expand All @@ -287,7 +288,7 @@ export default function Buzzer(props) {

<button
id="joinTeam2"
className="hover:shadow-md rounded-md bg-primary-200 p-5"
className={`hover:shadow-md rounded-md bg-primary-200 p-5 ${props.team === 1 ? 'border-2 border-sky-600' : ''}`}
onClick={() => {
props.setTeam(1);
}}
Expand All @@ -298,20 +299,12 @@ export default function Buzzer(props) {
<div className="flex flex-row justify-center">
<button
id="registerBuzzerButton"
className="py-8 px-16 hover:shadow-md rounded-md bg-success-200 uppercase font-bold"
disabled={props.team === null}
className={`py-8 px-16 hover:shadow-md rounded-md bg-success-200 uppercase font-bold ${props.team === null ? 'opacity-50 hover:shadow-none cursor-not-allowed': ''}`}
onClick={() => {
if (props.team != null) {
send({ action: "registerbuzz", team: props.team });
} else {
let errors = [];
props.team == null
? errors.push(t("pick your team"))
: null;
setError(errors.join(` ${t("and")} `));
if (props.id !== null && props.team !== null) {
setBuzzerReg(props.id);
}
}
}
}}
>
{t("play")}
Expand Down
2 changes: 1 addition & 1 deletion components/final.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function Answers(props) {
const { t } = useTranslation();
return props.round.map((x, i) => (
<div
key={`round-${i}`}
key={`final-round-answers-${i}`}
className="flex flex-row space-x-2"
style={{
minWidth: 0,
Expand Down
Loading

0 comments on commit 20905be

Please sign in to comment.