From 0c3b7f0f53a7a0db6fe6797f294a5b6f87be9405 Mon Sep 17 00:00:00 2001 From: Joshua Cold Date: Wed, 22 Jan 2025 08:13:31 -0700 Subject: [PATCH 01/12] fix(translation): fix for missing english translation --- public/locales/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 0a9619e5..97604eb2 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -94,7 +94,7 @@ "error reading file": "error reading file", "Unknown file type in game load": "Unknown file type in game load", ".json, .csv": ".json, .csv", - "Error: round and final round is greater than document has available.": "__STRING_NOT_TRANSLATED__", + "Error: round and final round is greater than document has available.": "Error: round and final round is greater than document has available.", "CSV file upload": "CSV file upload", "No Header": "No Header", "Rounds": "Rounds", From 23cc365623242a83eb850d741c1b1caf4832f4d1 Mon Sep 17 00:00:00 2001 From: Joshua Cold Date: Wed, 22 Jan 2025 08:13:54 -0700 Subject: [PATCH 02/12] fix(csv): csv parser function skips empty lines --- src/components/Admin/CSVLoader.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index 9f313d15..be832f5d 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -8,7 +8,15 @@ export function csvStringToArray(data) { const result = [[]]; let matches; while ((matches = re.exec(data))) { - if (matches[1].length && matches[1] !== ",") result.push([]); + // skip empty lines + if (!matches[3]) { + continue; + } + // push new array to results array + if (matches[1].length && matches[1] !== ",") { + result.push([]); + } + // push item to array within array result[result.length - 1].push(matches[2] !== undefined ? matches[2].replace(/""/g, '"') : matches[3]); } // remove last empty array element From 19d0f2522641c3f9bb273337673352c51f8bb87a Mon Sep 17 00:00:00 2001 From: Joshua Cold Date: Wed, 22 Jan 2025 08:40:22 -0700 Subject: [PATCH 03/12] feat(csv): on initalization of csv parser put in valid defaults for round count Set initial round count to -1 and on first load set up the default counts to fit within the data that the csv document has --- src/components/Admin/CSVLoader.jsx | 34 ++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index be832f5d..3cdb16fa 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -123,19 +123,49 @@ function csvToColdFriendlyFeudFormat(csvData, roundCount, roundFinalCount, noHea send({ action: "load_game", data: gameTemplate }); } +/** + * If first time loading, then count up the initial count of round and final round + * so that users start with a round and final round count + * that is less than their CSV document. + */ +function initalizeCSVRoundCount(csvData, roundCount, setRoundCount, roundFinalCount, setRoundFinalCount, noHeader) { + if (roundCount < 0 || roundFinalCount < 0) { + let headerOffSet = noHeader ? 0 : 1; + let _roundCount = 0; + let _roundCountDefaultLimit = 6; + let _finalRoundCount = 0; + let _finalRoundCountDefaultLimit = 4; + for (let _ in csvData) { + if (_roundCount < _roundCountDefaultLimit && _roundCount + headerOffSet < csvData.length) { + _roundCount++; + } else if ( + _finalRoundCount < _finalRoundCountDefaultLimit && + _finalRoundCount + _roundCount + headerOffSet < csvData.length + ) { + _finalRoundCount++; + } + } + console.log(_roundCount, _finalRoundCount, csvData.length); + setRoundCount(_roundCount); + setRoundFinalCount(_finalRoundCount); + } +} + export default function CSVLoader(props) { const { i18n, t } = useTranslation(); let csvData = csvStringToArray(props.csvFileUploadText); - const [roundCount, setRoundCount] = useState(6); - const [roundFinalCount, setRoundFinalCount] = useState(4); + const [roundCount, setRoundCount] = useState(-1); + const [roundFinalCount, setRoundFinalCount] = useState(-1); const [noHeader, setNoHeader] = useState(false); const [error, setError] = useState(false); const [timer, setTimer] = useState(20); const [timer2nd, setTimer2nd] = useState(25); useEffect(() => { setError(null); + initalizeCSVRoundCount(csvData, roundCount, setRoundCount, roundFinalCount, setRoundFinalCount, noHeader); validateCsv(csvData, roundCount, roundFinalCount, noHeader, setError, t); }); + let headerOffSet = noHeader ? 0 : 1; return (
From aed6d7cfe20b446e235028add3f2c81c854b37b2 Mon Sep 17 00:00:00 2001 From: Joshua Cold Date: Wed, 22 Jan 2025 08:41:38 -0700 Subject: [PATCH 04/12] fix(csv): don't allow the round count inputs go past csv doc length check if next value would be greater than csv doc and prevent input if greater --- src/components/Admin/CSVLoader.jsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index 3cdb16fa..3a694c40 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -271,9 +271,14 @@ export default function CSVLoader(props) { className="w-24 rounded bg-secondary-300 p-2 text-foreground" onChange={(e) => { let value = parseInt(e.target.value); + // Rounds must always be atleast 1 if (value === 0) { value = 1; } + // Don't allow more rounds than available data + if (value + roundFinalCount + headerOffSet > csvData.length) { + return; + } setRoundCount(value); }} type="number" @@ -290,6 +295,10 @@ export default function CSVLoader(props) { className="w-24 rounded bg-secondary-300 p-2 text-foreground" onChange={(e) => { let value = parseInt(e.target.value); + // Don't allow more rounds than available data + if (value + roundCount + headerOffSet > csvData.length) { + return; + } setRoundFinalCount(value); }} value={roundFinalCount} From cf3dce5ddb25040306f0fb2cca35dfd90e1fb749 Mon Sep 17 00:00:00 2001 From: Karl Romet Somelar Date: Thu, 23 Jan 2025 18:00:06 +0200 Subject: [PATCH 05/12] feat(csvloader): add dependencies to useEffect --- src/components/Admin/CSVLoader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index 3a694c40..3b0446aa 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -164,7 +164,7 @@ export default function CSVLoader(props) { setError(null); initalizeCSVRoundCount(csvData, roundCount, setRoundCount, roundFinalCount, setRoundFinalCount, noHeader); validateCsv(csvData, roundCount, roundFinalCount, noHeader, setError, t); - }); + }, [csvData, roundCount, roundFinalCount, noHeader, t]); let headerOffSet = noHeader ? 0 : 1; return (
From fe9c3abc7f8275559ea53360219b1386693a652b Mon Sep 17 00:00:00 2001 From: Karl Romet Somelar Date: Thu, 23 Jan 2025 18:06:14 +0200 Subject: [PATCH 06/12] chore(csvloader): fix tailwind linter errors --- src/components/Admin/CSVLoader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index 3b0446aa..aea3553b 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -167,7 +167,7 @@ export default function CSVLoader(props) { }, [csvData, roundCount, roundFinalCount, noHeader, t]); let headerOffSet = noHeader ? 0 : 1; return ( -
+
From 1afb9cad0f22a0a10a044659fd68a577a8f34192 Mon Sep 17 00:00:00 2001 From: Karl Romet Somelar Date: Thu, 23 Jan 2025 18:09:07 +0200 Subject: [PATCH 07/12] feat(a11y): add htmlFor in CSVLoader --- src/components/Admin/CSVLoader.jsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index aea3553b..59550d88 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -249,9 +249,9 @@ export default function CSVLoader(props) {
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+ Date: Thu, 23 Jan 2025 18:09:40 +0200 Subject: [PATCH 08/12] chore(csvloader): .log to .debug --- src/components/Admin/CSVLoader.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index 59550d88..bbacb013 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -83,7 +83,7 @@ function csvToColdFriendlyFeudFormat(csvData, roundCount, roundFinalCount, noHea let answer = true; let answerCount = 0; let index = parseInt(row) + parseInt(headerOffSet); - console.log(row, roundCount + roundFinalCount + headerOffSet); + console.debug(row, roundCount + roundFinalCount + headerOffSet); if (index < roundCount + headerOffSet) { colLoop: for (let col = 0; col < csvData[index].length; col++) { if (col === 0) { @@ -145,7 +145,7 @@ function initalizeCSVRoundCount(csvData, roundCount, setRoundCount, roundFinalCo _finalRoundCount++; } } - console.log(_roundCount, _finalRoundCount, csvData.length); + console.debug(_roundCount, _finalRoundCount, csvData.length); setRoundCount(_roundCount); setRoundFinalCount(_finalRoundCount); } From 70e6b81b933f73fc0d6975a0e64e6f464483c2e1 Mon Sep 17 00:00:00 2001 From: Karl Romet Somelar Date: Thu, 23 Jan 2025 18:45:27 +0200 Subject: [PATCH 09/12] refactor(csv): move each csv row to CSVRow.tsx --- src/components/Admin/CSVLoader.jsx | 51 +++++++----------------------- src/components/Admin/CSVRow.tsx | 49 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 39 deletions(-) create mode 100644 src/components/Admin/CSVRow.tsx diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index bbacb013..9efcd9bf 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -1,7 +1,8 @@ import { useTranslation } from "react-i18next"; import "@/i18n/i18n"; import { ERROR_CODES } from "@/i18n/errorCodes"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; +import CSVRow from "./CSVRow"; export function csvStringToArray(data) { const re = /(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))/gi; @@ -208,44 +209,16 @@ export default function CSVLoader(props) {
- {csvData.map((row, roundCounter) => { - return ( -
- {row.map((col, colidx) => { - let rowBackgroundColor = "bg-secondary-500"; - let rowTextColor = "text-foreground"; - let roundOffSet = 0; - if (noHeader) { - roundOffSet = -1; - } - if (roundCounter === 0 && !noHeader) { - rowTextColor = "text-secondary-900"; - } else if (roundCounter - 1 < roundCount + roundOffSet) { - rowBackgroundColor = "bg-success-200"; - } else if (roundCounter - 1 < roundCount + roundFinalCount + roundOffSet) { - rowBackgroundColor = "bg-primary-200"; - } else { - rowTextColor = "text-secondary-900"; - } - if (col.length !== 0) { - return ( -
-

{col}

-
- ); - } - })} -
- ); - })} + {csvData.map((row, roundCounter) => ( + + ))}
diff --git a/src/components/Admin/CSVRow.tsx b/src/components/Admin/CSVRow.tsx new file mode 100644 index 00000000..cb4a4ea2 --- /dev/null +++ b/src/components/Admin/CSVRow.tsx @@ -0,0 +1,49 @@ +import React from "react"; + +interface CSVRowProps { + row: string[]; + roundCounter: number; + noHeader: boolean; + roundCount: number; + roundFinalCount: number; +} + +const CSVRow: React.FC = ({ row, roundCounter, noHeader, roundCount, roundFinalCount }) => { + let rowBackgroundColor = "bg-secondary-500"; + let rowTextColor = "text-foreground"; + let roundOffSet = noHeader ? -1 : 0; + + if (roundCounter === 0 && !noHeader) { + rowTextColor = "text-secondary-900"; + } else if (roundCounter - 1 < roundCount + roundOffSet) { + rowBackgroundColor = "bg-success-200"; + } else if (roundCounter - 1 < roundCount + roundFinalCount + roundOffSet) { + rowBackgroundColor = "bg-primary-200"; + } else { + rowTextColor = "text-secondary-900"; + } + + return ( +
+ {row.map((col, colidx) => { + if (col.length === 0) return null; + + return ( +
+

{col}

+
+ ); + })} +
+ ); +}; + +export default CSVRow; From 0700543282d64d5a4bb49d2a9ecc14aec3e06a82 Mon Sep 17 00:00:00 2001 From: Karl Romet Somelar Date: Thu, 23 Jan 2025 18:53:31 +0200 Subject: [PATCH 10/12] feat(csv): use PapaParse - faster parsing - less boilerplate --- package-lock.json | 7 +++++++ package.json | 1 + src/components/Admin/CSVLoader.jsx | 28 ++++++++-------------------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5b0fba8..8b41c2bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "lucide-react": "^0.469.0", "next": "^14.2.23", "next-themes": "^0.4.4", + "papaparse": "^5.5.1", "postcss": "^8.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -5420,6 +5421,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/papaparse": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.1.tgz", + "integrity": "sha512-EuEKUhyxrHVozD7g3/ztsJn6qaKse8RPfR6buNB2dMJvdtXNhcw8jccVi/LxNEY3HVrV6GO6Z4OoeCG9Iy9wpA==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/package.json b/package.json index 55db6328..a65b5d6a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lucide-react": "^0.469.0", "next": "^14.2.23", "next-themes": "^0.4.4", + "papaparse": "^5.5.1", "postcss": "^8.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/components/Admin/CSVLoader.jsx b/src/components/Admin/CSVLoader.jsx index 9efcd9bf..46824446 100644 --- a/src/components/Admin/CSVLoader.jsx +++ b/src/components/Admin/CSVLoader.jsx @@ -1,28 +1,16 @@ import { useTranslation } from "react-i18next"; import "@/i18n/i18n"; import { ERROR_CODES } from "@/i18n/errorCodes"; -import { useEffect, useState } from "react"; +import Papa from "papaparse"; +import { useEffect, useMemo, useState } from "react"; import CSVRow from "./CSVRow"; export function csvStringToArray(data) { - const re = /(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))/gi; - const result = [[]]; - let matches; - while ((matches = re.exec(data))) { - // skip empty lines - if (!matches[3]) { - continue; - } - // push new array to results array - if (matches[1].length && matches[1] !== ",") { - result.push([]); - } - // push item to array within array - result[result.length - 1].push(matches[2] !== undefined ? matches[2].replace(/""/g, '"') : matches[3]); - } - // remove last empty array element - result.pop(); - return result; + const parsedData = Papa.parse(data, { + header: false, + skipEmptyLines: true, + }); + return parsedData.data; } /** @@ -154,7 +142,7 @@ function initalizeCSVRoundCount(csvData, roundCount, setRoundCount, roundFinalCo export default function CSVLoader(props) { const { i18n, t } = useTranslation(); - let csvData = csvStringToArray(props.csvFileUploadText); + const csvData = useMemo(() => csvStringToArray(props.csvFileUploadText), [props.csvFileUploadText]); const [roundCount, setRoundCount] = useState(-1); const [roundFinalCount, setRoundFinalCount] = useState(-1); const [noHeader, setNoHeader] = useState(false); From 8d26513d2be1a3a2c1bf027db9333e4ddfffde74 Mon Sep 17 00:00:00 2001 From: Karl Romet Somelar Date: Thu, 23 Jan 2025 18:54:57 +0200 Subject: [PATCH 11/12] fix(test): terminate after "can see mistakes" --- e2e/tests/admin.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/tests/admin.spec.js b/e2e/tests/admin.spec.js index a0adff32..68c22aa7 100644 --- a/e2e/tests/admin.spec.js +++ b/e2e/tests/admin.spec.js @@ -194,6 +194,8 @@ test("can see mistakes", async ({ browser }) => { expect(count1).toBe(2); expect(count2).toBe(1); }).toPass({ timeout: 5000 }); + + await adminPage.quitButton.click(); }); test("can use timer controls", async ({ browser }) => { From 0f9e834365487db3b618196cad5ea8040a68c1e8 Mon Sep 17 00:00:00 2001 From: Karl Romet Somelar Date: Thu, 23 Jan 2025 19:03:37 +0200 Subject: [PATCH 12/12] fix(buzzer): add coloring to blindfold text --- src/components/BuzzerPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BuzzerPage.jsx b/src/components/BuzzerPage.jsx index 3d907137..c37b5df3 100644 --- a/src/components/BuzzerPage.jsx +++ b/src/components/BuzzerPage.jsx @@ -125,7 +125,7 @@ export default function BuzzerPage(props) { const currentPlayer = game.registeredPlayers[props.id]; if (currentPlayer?.hidden) return ( -
+

{t("You have been blindfolded...")}