From 2c809d8d97b607cb5fd58479da6573e7f8c1a950 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Thu, 1 Aug 2024 00:36:04 +0700 Subject: [PATCH 01/28] refactor/Dockerfile and docker-compose.yaml --- .dockerignore | 1 + Dockerfile | 10 ++++------ docker-compose.yml | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/Dockerfile b/Dockerfile index addab69..f2b662d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,13 @@ -FROM node:18 +FROM node WORKDIR /eletypes_react -ADD package.json package-lock.json /eletypes_react +COPY package*.json ./ RUN npm install -ADD src/ ./src -ADD public/ ./public - +COPY . . EXPOSE 3000 -CMD [ "npm", "start" ] +CMD npm start diff --git a/docker-compose.yml b/docker-compose.yml index e9660ec..5accbbe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ -version: '3.3' services: eletypes-react: build: + context: . dockerfile: Dockerfile image: eletypes_react ports: From 7d5b723a47b1bcc338c95a793d37c4a2f4166093 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Tue, 6 Aug 2024 11:35:52 +0700 Subject: [PATCH 02/28] feat: add cool effect to improve user's focus --- src/components/features/TypeBox/TypeBox.js | 65 ++++++++++++++-------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 2c3d041..c88049e 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -1038,10 +1038,18 @@ const TypeBox = ({ >
{currentWords.map((word, i) => { + const opacityValue = Math.max( + 1 - Math.abs(i - currWordIndex) * 0.1, + 0.1 + ); return ( {word.split("").map((char, idx) => ( @@ -1065,29 +1073,42 @@ const TypeBox = ({ style={{ visibility: status === "finished" ? "hidden" : "visible" }} >
- {currentWords.map((word, i) => ( -
- { + const opacityValue = Math.max( + 1 - Math.abs(i - currWordIndex) * 0.1, + 0.1 + ); + + return ( +
- {" "} - {wordsKey[i]} - - - {word.split("").map((char, idx) => ( - - {char} - - ))} - {getExtraCharsDisplay(word, i)} - -
- ))} + + {" "} + {wordsKey[i]} + + + {word.split("").map((char, idx) => ( + + {char} + + ))} + {getExtraCharsDisplay(word, i)} + +
+ ); + })}
)} From bb287f0e87d912ad40d45ddc7cd4c1445de73e9f Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Fri, 16 Aug 2024 18:39:11 +0700 Subject: [PATCH 03/28] feat/add toggle focus mode --- src/components/features/TypeBox/TypeBox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index c88049e..234c31c 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -1047,7 +1047,7 @@ const TypeBox = ({ key={i} ref={wordSpanRefs[i]} style={{ - opacity: opacityValue, + opacity: isFocusedMode ? opacityValue : "1", transition: "500ms", }} className={getWordClassName(i)} From 2881b0ed74b97840571d29feb9a770a0556bb232 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Fri, 16 Aug 2024 19:26:04 +0700 Subject: [PATCH 04/28] feat/add ultra zen mode --- src/components/common/FooterMenu.js | 7 +++++-- src/style/global.js | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/common/FooterMenu.js b/src/components/common/FooterMenu.js index 6ee1ad9..b53eff3 100644 --- a/src/components/common/FooterMenu.js +++ b/src/components/common/FooterMenu.js @@ -75,8 +75,11 @@ const FooterMenu = ({ }; return ( - + handleKeyDown(e)} - onKeyUp={(e) => handleKeyUp(e)} - value={currInput} - onChange={(e) => UpdateInput(e)} - /> - - -
- press - Space{" "} - to redo + )} + {language === CHINESE_MODE && ( +
+
+ {currentWords.map((word, i) => { + const opacityValue = Math.max( + 1 - Math.abs(i - currWordIndex) * 0.1, + 0.1 + ); + + return ( +
+ + {" "} + {wordsKey[i]} + + + {word.split("").map((char, idx) => ( + + {char} + + ))} + {getExtraCharsDisplay(word, i)} + +
+ ); + })} +
-
+ )} +
+ + {status !== "finished" && renderResetButton()} +
+ handleKeyDown(e)} + onKeyUp={(e) => handleKeyUp(e)} + value={currInput} + onChange={(e) => UpdateInput(e)} + /> + + +
+ press + Space{" "} + to redo +
+
+ press + Tab{" "} + /{" "} + Enter{" "} + to restart +
press - Tab{" "} - /{" "} - Enter{" "} - to restart -
- press - any key {" "} - to exit - -
- + any key {" "} + to exit + + + + ); }; diff --git a/src/style/global.js b/src/style/global.js index beed306..a101f18 100644 --- a/src/style/global.js +++ b/src/style/global.js @@ -33,6 +33,42 @@ export const GlobalStyles = createGlobalStyle` padding: 1rem; transition: padding-top .125s; } +.fixed-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); /* Dark background with opacity */ + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; /* Ensure the overlay is on top */ +} + +.modal-content { + background: ${({ theme }) => theme.background}; + padding: 40px; /* Increased padding */ + border-radius: 8px; + position: relative; /* To position the close button */ + width: 80%; /* Increased width */ + max-width: 600px; /* Max width to keep it from getting too large */ + height: auto; /* Allow height to grow with content */ +} +.close-button { + color: ${({ theme }) => theme.textTypeBox}; +} +.modal-title { + margin-bottom: 20px; /* Add space below title */ +} + +.modal-description { + margin-bottom: 20px; /* Add space below description */ +} + +.modal-icons { + margin-top: 20px; /* Add space above icons */ +} .dynamicBackground { heigh: 100%; width: 100%; @@ -717,10 +753,10 @@ export const GlobalStyles = createGlobalStyle` } .fade-element { opacity: 0; - transition: opacity 500ms ease-in; + transition: opacity 500ms ease-in-out; } .fade-element:hover { opacity: 1; - transition: opacity 500ms ease-in; + transition: opacity 500ms ease-in-out; } `; From ddb936c12dfbbd182143b0dc5b6ba9b183a03385 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 00:25:39 +0700 Subject: [PATCH 07/28] feat/calculate wpm in web worker to optimize performance --- src/components/features/TypeBox/TypeBox.js | 13 ++++++++++--- src/worker/WorkerBuilder.js | 5 +++++ src/worker/calculateWpmWorker.js | 12 ++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/worker/WorkerBuilder.js create mode 100644 src/worker/calculateWpmWorker.js diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 4e63715..61b862e 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -47,6 +47,8 @@ import { } from "../../../constants/Constants"; import { SOUND_MAP } from "../sound/sound"; import SocialLinksModal from "../../common/SocialLinksModal"; +import WorkerBuilder from "../../../worker/WorkerBuilder"; +import calculateWpmWorker from "../../../worker/calculateWpmWorker"; const TypeBox = ({ textInputRef, @@ -450,9 +452,14 @@ const TypeBox = ({ // Update stats when typing unless there is no effective WPM if (wpmKeyStrokes !== 0 && countDownConstant - countDown !== 0) { - const currWpm = - (wpmKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0; - setWpm(currWpm); + const worker = WorkerBuilder(calculateWpmWorker); + + worker.postMessage({ wpmKeyStrokes, countDownConstant, countDown }); + + worker.onmessage = (event) => { + setWpm(event.data); + worker.terminate(); + }; } // start the game by typing any thing diff --git a/src/worker/WorkerBuilder.js b/src/worker/WorkerBuilder.js new file mode 100644 index 0000000..8737fc9 --- /dev/null +++ b/src/worker/WorkerBuilder.js @@ -0,0 +1,5 @@ +export default function WorkerBuilder(worker) { + const code = worker.toString(); + const blob = new Blob(["(" + code + ")()"]); + return new Worker(URL.createObjectURL(blob)); +} diff --git a/src/worker/calculateWpmWorker.js b/src/worker/calculateWpmWorker.js new file mode 100644 index 0000000..715c80a --- /dev/null +++ b/src/worker/calculateWpmWorker.js @@ -0,0 +1,12 @@ +// eslint-disable-next-line import/no-anonymous-default-export +export default () => { + // eslint-disable-next-line no-restricted-globals + self.onmessage = function (e) { + const { wpmKeyStrokes, countDownConstant, countDown } = e.data; + + const currWpm = + (wpmKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0; + + postMessage(currWpm); + }; +}; From d148dcca592e879e0887d1d5886df3a93ffa4f0b Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 12:35:29 +0700 Subject: [PATCH 08/28] feat/wokerize checkPrev function to improve performance and add modal timer will show modal in status === "finished" and user already played 5 minutes --- src/components/features/TypeBox/Stats.js | 58 +++--- src/components/features/TypeBox/TypeBox.js | 203 ++++++++++++++++----- src/worker/calculateWpmWorker.js | 2 + src/worker/checkPrevWorker.js | 68 +++++++ src/worker/getCharClassNameWorker.js | 76 ++++++++ src/worker/getWordClassNameWorker.js | 37 ++++ 6 files changed, 372 insertions(+), 72 deletions(-) create mode 100644 src/worker/checkPrevWorker.js create mode 100644 src/worker/getCharClassNameWorker.js create mode 100644 src/worker/getWordClassNameWorker.js diff --git a/src/components/features/TypeBox/Stats.js b/src/components/features/TypeBox/Stats.js index 4facabb..e6b64ca 100644 --- a/src/components/features/TypeBox/Stats.js +++ b/src/components/features/TypeBox/Stats.js @@ -27,17 +27,18 @@ const Stats = ({ incorrectCharsCount, }) => { const roundedWpm = Math.round( - (wpmKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0 + (wpmKeyStrokes / 5 / Math.max(countDownConstant - countDown, 1)) * 60.0 ); const roundedRawWpm = Math.round( - (rawKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0 + (rawKeyStrokes / 5 / Math.max(countDownConstant - countDown, 1)) * 60.0 ); + const initialTypingTestHistory = [ { wpm: 0, rawWpm: 0, - time: 0, // Start time from 0, but truncate time 0 when rendering + time: 0, error: 0, }, ]; @@ -50,17 +51,14 @@ const Stats = ({ const accuracy = Math.round(statsCharCount[0]); - const data = typingTestHistory.map((history) => { - return { - wpm: history.wpm, - rawWpm: history.rawWpm, - time: history.time, // Use the time property from history - error: history.error, - }; - }); + const data = typingTestHistory.map((history) => ({ + wpm: history.wpm, + rawWpm: history.rawWpm, + time: history.time, + error: history.error, + })); useEffect(() => { - // Reset history when user starts playing again if (status === "started") { setTypingTestHistory(initialTypingTestHistory); } @@ -168,13 +166,11 @@ const Stats = ({ ); - const renderIndicator = (color) => { - return ( - - ); - }; + const renderIndicator = (color) => ( + + ); const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { @@ -220,7 +216,7 @@ const Stats = ({

KPM

- {Math.round((rawKeyStrokes / countDownConstant) * 60.0)} + {Math.round((rawKeyStrokes / Math.max(countDownConstant, 1)) * 60.0)}

); @@ -241,16 +237,16 @@ const Stats = ({ ); - const renderWpm = () => ( -
-

WPM

-

- {Math.round( - data.map((e) => e.wpm).reduce((a, b) => a + b, 0) / (data.length - 1) - )} -

-
- ); + const renderWpm = () => { + const totalWpm = data.map((e) => e.wpm).reduce((a, b) => a + b, 0); + const averageWpm = data.length > 1 ? totalWpm / (data.length - 1) : 0; + return ( +
+

WPM

+

{Math.round(averageWpm)}

+
+ ); + }; const Chart = () => ( - } />{" "} + } /> { + // Initialize worker + wpmWorkerRef.current = WorkerBuilder(calculateWpmWorker); + + return () => { + // Cleanup worker on component unmount + if (wpmWorkerRef.current) { + wpmWorkerRef.current.terminate(); + } + }; + }, []); + + const calculateWpm = (wpmKeyStrokes, countDownConstant, countDown) => { + if (wpmKeyStrokes !== 0 && countDownConstant - countDown !== 0) { + if (!wpmWorkerRef.current) return; // Ensure worker is initialized + + wpmWorkerRef.current.postMessage({ + wpmKeyStrokes, + countDownConstant, + countDown, + }); + + wpmWorkerRef.current.onmessage = (event) => { + console.log(event.data); + setWpm(event.data); + }; + + wpmWorkerRef.current.onerror = (error) => { + console.error("Worker error:", error); + }; + } + }; + const handleKeyDown = (e) => { if (status !== "finished" && soundMode) { play(); @@ -452,14 +488,7 @@ const TypeBox = ({ // Update stats when typing unless there is no effective WPM if (wpmKeyStrokes !== 0 && countDownConstant - countDown !== 0) { - const worker = WorkerBuilder(calculateWpmWorker); - - worker.postMessage({ wpmKeyStrokes, countDownConstant, countDown }); - - worker.onmessage = (event) => { - setWpm(event.data); - worker.terminate(); - }; + calculateWpm(wpmKeyStrokes, countDownConstant, countDown); } // start the game by typing any thing @@ -557,37 +586,56 @@ const TypeBox = ({ } }; + const workerRef = useRef(null); + + useEffect(() => { + workerRef.current = WorkerBuilder(checkPrevWorker); + + return () => { + if (workerRef.current) { + workerRef.current.terminate(); + } + }; + }, []); + const checkPrev = () => { - const wordToCompare = words[currWordIndex]; - const currInputWithoutSpaces = currInput.trim(); - const isCorrect = wordToCompare === currInputWithoutSpaces; - if (!currInputWithoutSpaces || currInputWithoutSpaces.length === 0) { - return null; - } - if (isCorrect) { - // console.log("detected match"); - wordsCorrect.add(currWordIndex); - wordsInCorrect.delete(currWordIndex); - let inputWordsHistoryUpdate = { ...inputWordsHistory }; - inputWordsHistoryUpdate[currWordIndex] = currInputWithoutSpaces; - setInputWordsHistory(inputWordsHistoryUpdate); - // reset prevInput to empty (will not go back) - setPrevInput(""); + if (!workerRef.current) return; - // here count the space as effective wpm. - setWpmKeyStrokes(wpmKeyStrokes + 1); - return true; - } else { - // console.log("detected unmatch"); - wordsInCorrect.add(currWordIndex); - wordsCorrect.delete(currWordIndex); - let inputWordsHistoryUpdate = { ...inputWordsHistory }; - inputWordsHistoryUpdate[currWordIndex] = currInputWithoutSpaces; - setInputWordsHistory(inputWordsHistoryUpdate); - // append currInput to prevInput - setPrevInput(prevInput + " " + currInputWithoutSpaces); - return false; - } + const currInputWithoutSpaces = currInput.trim(); + workerRef.current.postMessage({ + words, + currWordIndex, + currInputWithoutSpaces, + wordsCorrect: Array.from(wordsCorrect), + wordsInCorrect: Array.from(wordsInCorrect), + inputWordsHistory, + prevInput, + wpmKeyStrokes, + }); + + workerRef.current.onmessage = (event) => { + const { + isCorrect, + updatedWordsCorrect, + updatedWordsInCorrect, + updatedInputWordsHistory, + updatedPrevInput, + updatedWpmKeyStrokes, + } = event.data; + + if (isCorrect !== null) { + setWordsCorrect(new Set(updatedWordsCorrect)); + setWordsInCorrect(new Set(updatedWordsInCorrect)); + setInputWordsHistory(updatedInputWordsHistory); + setPrevInput(updatedPrevInput); + setWpmKeyStrokes(updatedWpmKeyStrokes); + + // Always move to the next word + setCurrWordIndex((prevIndex) => prevIndex + 1); + setCurrInput(""); + setCurrCharIndex(-1); + } + }; }; const getWordClassName = (wordIdx) => { @@ -1045,6 +1093,74 @@ const TypeBox = ({ } }, [currWordIndex]); + const MODAL_DISPLAY_KEY = "modalDisplayedTimestamp"; // Key for local storage + const COUNTDOWN_KEY = "countdownStartTime"; // Key for countdown start time + const COUNTDOWN_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds + + const [isShouldShowModal, setIsShouldShowModal] = useState(false); + const [remainingTime, setRemainingTime] = useState(COUNTDOWN_DURATION); + + const checkIfModalShouldBeDisplayed = () => { + const lastDisplayed = localStorage.getItem(MODAL_DISPLAY_KEY); + const countdownStartTime = parseInt( + localStorage.getItem(COUNTDOWN_KEY), + 10 + ); + const now = new Date().getTime(); + + if ( + !lastDisplayed || + now - parseInt(lastDisplayed, 10) >= COUNTDOWN_DURATION + ) { + // Show modal and record timestamp + setIsShouldShowModal(true); + localStorage.setItem(MODAL_DISPLAY_KEY, now.toString()); + } + }; + + const startCountdown = () => { + const now = new Date().getTime(); + localStorage.setItem(COUNTDOWN_KEY, now.toString()); + checkIfModalShouldBeDisplayed(); + }; + + const calculateRemainingTime = () => { + const countdownStartTime = parseInt( + localStorage.getItem(COUNTDOWN_KEY), + 10 + ); + const now = new Date().getTime(); + const elapsedTime = now - countdownStartTime; + const timeLeft = COUNTDOWN_DURATION - elapsedTime; + + if (timeLeft <= 0) { + setRemainingTime(0); + localStorage.removeItem(COUNTDOWN_KEY); // Clear the countdown start time + setIsShouldShowModal(true); + localStorage.setItem(MODAL_DISPLAY_KEY, now.toString()); // Update modal display timestamp + } else { + setRemainingTime(timeLeft); + } + }; + + const formatTime = (time) => { + const minutes = Math.floor(time / 60000); + const seconds = Math.floor((time % 60000) / 1000); + return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`; + }; + + useEffect(() => { + calculateRemainingTime(); // Initial calculation + const timer = setInterval(calculateRemainingTime, 1000); // Update every second + + // Start countdown if not already started + if (!localStorage.getItem(COUNTDOWN_KEY)) { + startCountdown(); + } + + return () => clearInterval(timer); // Cleanup on component unmount + }, []); + useEffect(() => { const body = document.getElementsByTagName("body")[0]; const delay = 500; @@ -1072,13 +1188,18 @@ const TypeBox = ({ body.style.cursor = "default"; // Reset cursor style on cleanup }; } else { - if (status === "finished") { - handleOpenModal(); + // Reset countdown and show modal if finished and should show modal + if (status === "finished" && isShouldShowModal) { + setIsShouldShowModal(false); // Hide the modal + localStorage.removeItem(COUNTDOWN_KEY); // Reset countdown start time + localStorage.removeItem(MODAL_DISPLAY_KEY); // Clear modal display timestamp + setRemainingTime(COUNTDOWN_DURATION); // Reset remaining time + handleOpenModal() } // Ensure cursor is reset if status is not "started" body.style.cursor = "default"; } - }, [status]); + }, [status, isShouldShowModal]); return ( <> diff --git a/src/worker/calculateWpmWorker.js b/src/worker/calculateWpmWorker.js index 715c80a..b1ff9b5 100644 --- a/src/worker/calculateWpmWorker.js +++ b/src/worker/calculateWpmWorker.js @@ -4,6 +4,8 @@ export default () => { self.onmessage = function (e) { const { wpmKeyStrokes, countDownConstant, countDown } = e.data; + console.log(wpmKeyStrokes); + const currWpm = (wpmKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0; diff --git a/src/worker/checkPrevWorker.js b/src/worker/checkPrevWorker.js new file mode 100644 index 0000000..3fb8b68 --- /dev/null +++ b/src/worker/checkPrevWorker.js @@ -0,0 +1,68 @@ +// eslint-disable-next-line import/no-anonymous-default-export +export default () => { + // eslint-disable-next-line no-restricted-globals + self.onmessage = function (event) { + const { + words, + currWordIndex, + currInputWithoutSpaces, + wordsCorrect, + wordsInCorrect, + inputWordsHistory, + prevInput, + wpmKeyStrokes, + } = event.data; + + console.log(event.data); + + // Validate input data + const wordToCompare = words[currWordIndex]; + const isCorrect = wordToCompare === currInputWithoutSpaces; + + // Ensure wpmKeyStrokes is a valid number + const validWpmKeyStrokes = + typeof wpmKeyStrokes === "number" && !isNaN(wpmKeyStrokes) + ? wpmKeyStrokes + : 0; + + if (!currInputWithoutSpaces || currInputWithoutSpaces.length === 0) { + postMessage({ isCorrect: null }); + return; + } + + let updatedWordsCorrect = new Set(wordsCorrect); + let updatedWordsInCorrect = new Set(wordsInCorrect); + let updatedInputWordsHistory = { ...inputWordsHistory }; + let updatedPrevInput = prevInput; + let updatedWpmKeyStrokes = validWpmKeyStrokes; + + if (isCorrect) { + updatedWordsCorrect.add(currWordIndex); + updatedWordsInCorrect.delete(currWordIndex); + updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; + updatedPrevInput = ""; + updatedWpmKeyStrokes += 1; // Increment only if valid + postMessage({ + isCorrect: true, + updatedWordsCorrect: Array.from(updatedWordsCorrect), + updatedWordsInCorrect: Array.from(updatedWordsInCorrect), + updatedInputWordsHistory, + updatedPrevInput, + updatedWpmKeyStrokes, + }); + } else { + updatedWordsInCorrect.add(currWordIndex); + updatedWordsCorrect.delete(currWordIndex); + updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; + updatedPrevInput += " " + currInputWithoutSpaces; + postMessage({ + isCorrect: false, + updatedWordsCorrect: Array.from(updatedWordsCorrect), + updatedWordsInCorrect: Array.from(updatedWordsInCorrect), + updatedInputWordsHistory, + updatedPrevInput, + updatedWpmKeyStrokes, + }); + } + }; +}; diff --git a/src/worker/getCharClassNameWorker.js b/src/worker/getCharClassNameWorker.js new file mode 100644 index 0000000..cd861bf --- /dev/null +++ b/src/worker/getCharClassNameWorker.js @@ -0,0 +1,76 @@ +// eslint-disable-next-line import/no-anonymous-default-export +export default () => { + // eslint-disable-next-line no-restricted-globals + self.onmessage = function (e) { + const { + wordIdx, + charIdx, + pacingStyle, + PACING_CARET, + currWordIndex, + currCharIndex, + status, + history, + char, + currChar, + word, + } = e.data; + + const keyString = wordIdx + "." + charIdx; + let className; + + if ( + pacingStyle === PACING_CARET && + wordIdx === currWordIndex && + charIdx === currCharIndex + 1 && + status !== "finished" + ) { + className = "caret-char-left"; + } else if (history[keyString] === true) { + if ( + pacingStyle === PACING_CARET && + wordIdx === currWordIndex && + word.length - 1 === currCharIndex && + charIdx === currCharIndex && + status !== "finished" + ) { + className = "caret-char-right-correct"; + } else { + className = "correct-char"; + } + } else if (history[keyString] === false) { + if ( + pacingStyle === PACING_CARET && + wordIdx === currWordIndex && + word.length - 1 === currCharIndex && + charIdx === currCharIndex && + status !== "finished" + ) { + className = "caret-char-right-error"; + } else { + className = "error-char"; + } + } else if ( + wordIdx === currWordIndex && + charIdx === currCharIndex && + currChar && + status !== "finished" + ) { + if (char === currChar) { + history[keyString] = true; + className = "correct-char"; + } else { + history[keyString] = false; + className = "error-char"; + } + } else { + if (wordIdx < currWordIndex) { + // missing chars + history[keyString] = undefined; + } + className = "char"; + } + + postMessage(className); + }; +}; diff --git a/src/worker/getWordClassNameWorker.js b/src/worker/getWordClassNameWorker.js new file mode 100644 index 0000000..bf725e4 --- /dev/null +++ b/src/worker/getWordClassNameWorker.js @@ -0,0 +1,37 @@ +// eslint-disable-next-line import/no-anonymous-default-export +export default () => { + // eslint-disable-next-line no-restricted-globals + self.onmessage = function (e) { + const { + wordsInCorrect, + wordIdx, + currWordIndex, + pacingStyle, + PACING_PULSE, + } = e.data; + + let className = "word"; + + if (wordsInCorrect.has(wordIdx)) { + if (currWordIndex === wordIdx) { + if (pacingStyle === PACING_PULSE) { + className = "word error-word active-word"; + } else { + className = "word error-word active-word-no-pulse"; + } + } else { + className = "word error-word"; + } + } else { + if (currWordIndex === wordIdx) { + if (pacingStyle === PACING_PULSE) { + className = "word active-word"; + } else { + className = "word active-word-no-pulse"; + } + } + } + + postMessage(className); + }; +}; From 1eb1316c6e8d695c076d46411a726c5d888bfde1 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 12:38:05 +0700 Subject: [PATCH 09/28] remove console.logs --- src/components/features/TypeBox/TypeBox.js | 3 +-- src/worker/calculateWpmWorker.js | 2 -- src/worker/checkPrevWorker.js | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 900775b..1695f6b 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -435,7 +435,6 @@ const TypeBox = ({ }); wpmWorkerRef.current.onmessage = (event) => { - console.log(event.data); setWpm(event.data); }; @@ -1194,7 +1193,7 @@ const TypeBox = ({ localStorage.removeItem(COUNTDOWN_KEY); // Reset countdown start time localStorage.removeItem(MODAL_DISPLAY_KEY); // Clear modal display timestamp setRemainingTime(COUNTDOWN_DURATION); // Reset remaining time - handleOpenModal() + handleOpenModal(); } // Ensure cursor is reset if status is not "started" body.style.cursor = "default"; diff --git a/src/worker/calculateWpmWorker.js b/src/worker/calculateWpmWorker.js index b1ff9b5..715c80a 100644 --- a/src/worker/calculateWpmWorker.js +++ b/src/worker/calculateWpmWorker.js @@ -4,8 +4,6 @@ export default () => { self.onmessage = function (e) { const { wpmKeyStrokes, countDownConstant, countDown } = e.data; - console.log(wpmKeyStrokes); - const currWpm = (wpmKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0; diff --git a/src/worker/checkPrevWorker.js b/src/worker/checkPrevWorker.js index 3fb8b68..ded079b 100644 --- a/src/worker/checkPrevWorker.js +++ b/src/worker/checkPrevWorker.js @@ -13,8 +13,6 @@ export default () => { wpmKeyStrokes, } = event.data; - console.log(event.data); - // Validate input data const wordToCompare = words[currWordIndex]; const isCorrect = wordToCompare === currInputWithoutSpaces; From a68f7ca1c12165ee082687feec0a13cc08496543 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 12:48:51 +0700 Subject: [PATCH 10/28] remove unnecessary code --- src/components/features/TypeBox/TypeBox.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 1695f6b..8a12a18 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -704,10 +704,6 @@ const TypeBox = ({ return setIncorrectCharsCount((prev) => prev + 1); }, [currChar, status, currCharIndex]); - useEffect(() => { - // console.log("incorrectCharsCount:", incorrectCharsCount); - }, [incorrectCharsCount]); - const getCharClassName = (wordIdx, charIdx, char, word) => { const keyString = wordIdx + "." + charIdx; if ( From 1a4e7d0ca80671e877ac88b08b2a22af8a82cc82 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 13:09:07 +0700 Subject: [PATCH 11/28] fix web worker --- package-lock.json | 19 ++++++++++++------- src/components/features/TypeBox/TypeBox.js | 4 ++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd11b31..f459552 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5518,9 +5518,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001331", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001331.tgz", - "integrity": "sha512-Y1xk6paHpUXKP/P6YjQv1xqyTbgAP05ycHBcRdQjTcyXlWol868sJJPlmk5ylOekw2BrucWes5jk+LvVd7WZ5Q==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "funding": [ { "type": "opencollective", @@ -5529,8 +5529,13 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -20738,9 +20743,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001331", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001331.tgz", - "integrity": "sha512-Y1xk6paHpUXKP/P6YjQv1xqyTbgAP05ycHBcRdQjTcyXlWol868sJJPlmk5ylOekw2BrucWes5jk+LvVd7WZ5Q==" + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 8a12a18..8fd5180 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -414,7 +414,7 @@ const TypeBox = ({ useEffect(() => { // Initialize worker - wpmWorkerRef.current = WorkerBuilder(calculateWpmWorker); + wpmWorkerRef.current = new WorkerBuilder(calculateWpmWorker); return () => { // Cleanup worker on component unmount @@ -588,7 +588,7 @@ const TypeBox = ({ const workerRef = useRef(null); useEffect(() => { - workerRef.current = WorkerBuilder(checkPrevWorker); + workerRef.current = new WorkerBuilder(checkPrevWorker); return () => { if (workerRef.current) { From f0693d90b66849a3bdbbddc70180cc5f7568b23f Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 13:46:32 +0700 Subject: [PATCH 12/28] fix --- src/worker/checkPrevWorker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/worker/checkPrevWorker.js b/src/worker/checkPrevWorker.js index ded079b..9849341 100644 --- a/src/worker/checkPrevWorker.js +++ b/src/worker/checkPrevWorker.js @@ -23,6 +23,7 @@ export default () => { ? wpmKeyStrokes : 0; + // Check if the current input is empty if (!currInputWithoutSpaces || currInputWithoutSpaces.length === 0) { postMessage({ isCorrect: null }); return; From 6eea64a4bc6cd008af797b500955474c730f4ecc Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 18:56:01 +0700 Subject: [PATCH 13/28] improve performance --- .firebase/hosting.YnVpbGQ.cache | 25 +++++ .firebaserc | 5 + firebase.json | 16 +++ src/components/features/TypeBox/TypeBox.js | 11 +- src/worker/calculateWpmWorker.js | 14 +-- src/worker/checkPrevWorker.js | 119 ++++++++++----------- 6 files changed, 115 insertions(+), 75 deletions(-) create mode 100644 .firebase/hosting.YnVpbGQ.cache create mode 100644 .firebaserc create mode 100644 firebase.json diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache new file mode 100644 index 0000000..dd9fe52 --- /dev/null +++ b/.firebase/hosting.YnVpbGQ.cache @@ -0,0 +1,25 @@ +robots.txt,1723894396770,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +manifest.json,1723894396770,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +index.html,1723894506764,88f92d47b9d678079277fab47a8ef93cc03483a7d668787adcc2d2c47c9f585e +logo.png,1723894396770,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +asset-manifest.json,1723894506807,e3915da7985732e2d1492a309eeed87f57955959392ffef3987da9fbdf4ec9e0 +logo512.png,1723894396770,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +logo192.png,1723894396770,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +favicon.ico,1723894396770,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +static/media/typeSoft.3e307070c0d43972460c.wav,1723894506797,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +static/js/main.1c05fc1f.js.LICENSE.txt,1723894506797,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723894506797,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723894506797,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/181.50cd7f54.chunk.js.map,1723894506807,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c +static/js/181.50cd7f54.chunk.js,1723894506797,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 +static/js/110.443f5259.chunk.js.map,1723894506804,26d05d3583ecd4920bff67743bfdb6d44246091c78fdbaec34ce37b3296d3548 +static/js/110.443f5259.chunk.js,1723894506797,b65c7950a74afd66738cfb80d377ad39536b2558462467b7554f7d731175019a +static/css/main.e6c13ad2.css.map,1723894506797,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/css/main.e6c13ad2.css,1723894506797,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/js/845.d88f7a05.chunk.js,1723894506797,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723894506797,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723894506797,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723894506797,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/845.d88f7a05.chunk.js.map,1723894506807,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/js/main.1c05fc1f.js,1723894506797,d98c8eff65ef54b21b23a9dd330abedbf82d961eae2d473bdf6400c8233a044a +static/js/main.1c05fc1f.js.map,1723894506797,9e4de2c25b634c0ae50f09783d8cd7f6f23963c87e5e88ad0f045347d376ee26 diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..b1028d3 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "eletypes" + } +} diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..340ed5b --- /dev/null +++ b/firebase.json @@ -0,0 +1,16 @@ +{ + "hosting": { + "public": "build", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 8fd5180..39b4c76 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -47,9 +47,6 @@ import { } from "../../../constants/Constants"; import { SOUND_MAP } from "../sound/sound"; import SocialLinksModal from "../../common/SocialLinksModal"; -import WorkerBuilder from "../../../worker/WorkerBuilder"; -import calculateWpmWorker from "../../../worker/calculateWpmWorker"; -import checkPrevWorker from "../../../worker/checkPrevWorker"; const TypeBox = ({ textInputRef, @@ -414,7 +411,9 @@ const TypeBox = ({ useEffect(() => { // Initialize worker - wpmWorkerRef.current = new WorkerBuilder(calculateWpmWorker); + wpmWorkerRef.current = new Worker( + new URL("../../../worker/calculateWpmWorker", import.meta.url) + ); return () => { // Cleanup worker on component unmount @@ -588,7 +587,9 @@ const TypeBox = ({ const workerRef = useRef(null); useEffect(() => { - workerRef.current = new WorkerBuilder(checkPrevWorker); + workerRef.current = new Worker( + new URL("../../../worker/checkPrevWorker", import.meta.url) + ); return () => { if (workerRef.current) { diff --git a/src/worker/calculateWpmWorker.js b/src/worker/calculateWpmWorker.js index 715c80a..def38a3 100644 --- a/src/worker/calculateWpmWorker.js +++ b/src/worker/calculateWpmWorker.js @@ -1,12 +1,8 @@ -// eslint-disable-next-line import/no-anonymous-default-export -export default () => { - // eslint-disable-next-line no-restricted-globals - self.onmessage = function (e) { - const { wpmKeyStrokes, countDownConstant, countDown } = e.data; +// eslint-disable-next-line no-restricted-globals +self.onmessage = function (e) { + const { wpmKeyStrokes, countDownConstant, countDown } = e.data; - const currWpm = - (wpmKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0; + const currWpm = (wpmKeyStrokes / 5 / (countDownConstant - countDown)) * 60.0; - postMessage(currWpm); - }; + postMessage(currWpm); }; diff --git a/src/worker/checkPrevWorker.js b/src/worker/checkPrevWorker.js index 9849341..723ceed 100644 --- a/src/worker/checkPrevWorker.js +++ b/src/worker/checkPrevWorker.js @@ -1,67 +1,64 @@ -// eslint-disable-next-line import/no-anonymous-default-export -export default () => { - // eslint-disable-next-line no-restricted-globals - self.onmessage = function (event) { - const { - words, - currWordIndex, - currInputWithoutSpaces, - wordsCorrect, - wordsInCorrect, - inputWordsHistory, - prevInput, - wpmKeyStrokes, - } = event.data; +// eslint-disable-next-line no-restricted-globals +self.onmessage = function (event) { + const { + words, + currWordIndex, + currInputWithoutSpaces, + wordsCorrect, + wordsInCorrect, + inputWordsHistory, + prevInput, + wpmKeyStrokes, + } = event.data; - // Validate input data - const wordToCompare = words[currWordIndex]; - const isCorrect = wordToCompare === currInputWithoutSpaces; + // Validate input data + const wordToCompare = words[currWordIndex]; + const isCorrect = wordToCompare === currInputWithoutSpaces; - // Ensure wpmKeyStrokes is a valid number - const validWpmKeyStrokes = - typeof wpmKeyStrokes === "number" && !isNaN(wpmKeyStrokes) - ? wpmKeyStrokes - : 0; + // Ensure wpmKeyStrokes is a valid number + const validWpmKeyStrokes = + typeof wpmKeyStrokes === "number" && !isNaN(wpmKeyStrokes) + ? wpmKeyStrokes + : 0; - // Check if the current input is empty - if (!currInputWithoutSpaces || currInputWithoutSpaces.length === 0) { - postMessage({ isCorrect: null }); - return; - } + // Check if the current input is empty + if (!currInputWithoutSpaces || currInputWithoutSpaces.length === 0) { + postMessage({ isCorrect: null }); + return; + } - let updatedWordsCorrect = new Set(wordsCorrect); - let updatedWordsInCorrect = new Set(wordsInCorrect); - let updatedInputWordsHistory = { ...inputWordsHistory }; - let updatedPrevInput = prevInput; - let updatedWpmKeyStrokes = validWpmKeyStrokes; + let updatedWordsCorrect = new Set(wordsCorrect); + let updatedWordsInCorrect = new Set(wordsInCorrect); + let updatedInputWordsHistory = { ...inputWordsHistory }; + let updatedPrevInput = prevInput; + let updatedWpmKeyStrokes = validWpmKeyStrokes; - if (isCorrect) { - updatedWordsCorrect.add(currWordIndex); - updatedWordsInCorrect.delete(currWordIndex); - updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; - updatedPrevInput = ""; - updatedWpmKeyStrokes += 1; // Increment only if valid - postMessage({ - isCorrect: true, - updatedWordsCorrect: Array.from(updatedWordsCorrect), - updatedWordsInCorrect: Array.from(updatedWordsInCorrect), - updatedInputWordsHistory, - updatedPrevInput, - updatedWpmKeyStrokes, - }); - } else { - updatedWordsInCorrect.add(currWordIndex); - updatedWordsCorrect.delete(currWordIndex); - updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; - updatedPrevInput += " " + currInputWithoutSpaces; - postMessage({ - isCorrect: false, - updatedWordsCorrect: Array.from(updatedWordsCorrect), - updatedWordsInCorrect: Array.from(updatedWordsInCorrect), - updatedInputWordsHistory, - updatedPrevInput, - updatedWpmKeyStrokes, - }); - } - }; + if (isCorrect) { + updatedWordsCorrect.add(currWordIndex); + updatedWordsInCorrect.delete(currWordIndex); + updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; + updatedPrevInput = ""; + updatedWpmKeyStrokes += 1; // Increment only if valid + postMessage({ + isCorrect: true, + updatedWordsCorrect: Array.from(updatedWordsCorrect), + updatedWordsInCorrect: Array.from(updatedWordsInCorrect), + updatedInputWordsHistory, + updatedPrevInput, + updatedWpmKeyStrokes, + }); + } else { + updatedWordsInCorrect.add(currWordIndex); + updatedWordsCorrect.delete(currWordIndex); + updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; + updatedPrevInput += " " + currInputWithoutSpaces; + postMessage({ + isCorrect: false, + updatedWordsCorrect: Array.from(updatedWordsCorrect), + updatedWordsInCorrect: Array.from(updatedWordsInCorrect), + updatedInputWordsHistory, + updatedPrevInput, + updatedWpmKeyStrokes, + }); + } }; From 906bc300bfe350c6859e96541e72bcd2f29bd3df Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 21:59:24 +0700 Subject: [PATCH 14/28] feat/improve performance with web worker --- src/algorithm.md | 5 -- src/components/features/TypeBox/TypeBox.js | 57 +++++++++------- src/scripts/wordsGenerator.js | 58 +++++++--------- src/worker/wordsGeneratorWorker.js | 77 ++++++++++++++++++++++ 4 files changed, 136 insertions(+), 61 deletions(-) delete mode 100644 src/algorithm.md create mode 100644 src/worker/wordsGeneratorWorker.js diff --git a/src/algorithm.md b/src/algorithm.md deleted file mode 100644 index 957471e..0000000 --- a/src/algorithm.md +++ /dev/null @@ -1,5 +0,0 @@ -# Algorithm to get total speed target wpm - -1. Get target wpm -2. Get to know how to calculate wpm -3. diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 39b4c76..375f5e9 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -150,25 +150,34 @@ const TypeBox = ({ }; // set up words state - const [wordsDict, setWordsDict] = useState(() => { + const [wordsDict, setWordsDict] = useState([]); + + useEffect(() => { if (language === ENGLISH_MODE) { - return wordsGenerator( - DEFAULT_WORDS_COUNT, - difficulty, - ENGLISH_MODE, - numberAddOn, - symbolAddOn - ); + (async () => { + const generatedWords = await wordsGenerator( + DEFAULT_WORDS_COUNT, + difficulty, + ENGLISH_MODE, + numberAddOn, + symbolAddOn + ); + + setWordsDict(generatedWords); + })(); } + if (language === CHINESE_MODE) { - return chineseWordsGenerator( + const generatedWords = chineseWordsGenerator( difficulty, CHINESE_MODE, numberAddOn, symbolAddOn ); + + setWordsDict(generatedWords); } - }); + }, []); const words = useMemo(() => { return wordsDict.map((e) => e.val); @@ -223,14 +232,16 @@ const TypeBox = ({ useEffect(() => { if (currWordIndex === DEFAULT_WORDS_COUNT - 1) { if (language === ENGLISH_MODE) { - const generatedEng = wordsGenerator( - DEFAULT_WORDS_COUNT, - difficulty, - ENGLISH_MODE, - numberAddOn, - symbolAddOn - ); - setWordsDict((currentArray) => [...currentArray, ...generatedEng]); + (async () => { + const generatedEng = await wordsGenerator( + DEFAULT_WORDS_COUNT, + difficulty, + ENGLISH_MODE, + numberAddOn, + symbolAddOn + ); + setWordsDict((currentArray) => [...currentArray, ...generatedEng]); + })(); } if (language === CHINESE_MODE) { const generatedChinese = chineseWordsGenerator( @@ -281,15 +292,17 @@ const TypeBox = ({ ); } if (language === ENGLISH_MODE) { - setWordsDict( - wordsGenerator( + (async () => { + const generatedWords = await wordsGenerator( DEFAULT_WORDS_COUNT, difficulty, language, newNumberAddOn, newSymbolAddOn - ) - ); + ); + + setWordsDict(generatedWords); + })(); } } setNumberAddOn(newNumberAddOn); diff --git a/src/scripts/wordsGenerator.js b/src/scripts/wordsGenerator.js index f9d8ff2..f95f091 100644 --- a/src/scripts/wordsGenerator.js +++ b/src/scripts/wordsGenerator.js @@ -27,42 +27,32 @@ const wordsGenerator = ( numberAddOn, symbolAddOn ) => { - if (languageMode === ENGLISH_MODE) { - if (difficulty === DEFAULT_DIFFICULTY) { - const EnglishWordList = []; - for (let i = 0; i < DEFAULT_WORDS_COUNT; i++) { - const rand = randomIntFromRange(0, 550); - let wordCandidate = COMMON_WORDS[rand].val; - if (numberAddOn) { - wordCandidate = wordCandidate + generateRandomNumChras(1, 2); - } - if (symbolAddOn) { - wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); - } - EnglishWordList.push({ key: wordCandidate, val: wordCandidate }); - } - return EnglishWordList; - } + return new Promise((resolve, reject) => { + const worker = new Worker( + new URL("../worker/wordsGeneratorWorker.js", import.meta.url) + ); + + worker.onmessage = function (e) { + const generatedWords = e.data; + resolve(generatedWords); + }; - // hard - const randomWordsGenerated = randomWords({ - exactly: wordsCount, - maxLength: 7, + worker.onerror = function (e) { + reject(e); + }; + + worker.postMessage({ + wordsCount, + ENGLISH_MODE, + COMMON_WORDS, + DEFAULT_DIFFICULTY, + DEFAULT_WORDS_COUNT, + difficulty, + languageMode, + numberAddOn, + symbolAddOn, }); - const words = []; - for (let i = 0; i < wordsCount; i++) { - let wordCandidate = randomWordsGenerated[i]; - if (numberAddOn) { - wordCandidate = wordCandidate + generateRandomNumChras(1, 2); - } - if (symbolAddOn) { - wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); - } - words.push({ key: wordCandidate, val: wordCandidate }); - } - return words; - } - return ["something", "went", "wrong"]; + }); }; const chineseWordsGenerator = ( diff --git a/src/worker/wordsGeneratorWorker.js b/src/worker/wordsGeneratorWorker.js new file mode 100644 index 0000000..eabd47f --- /dev/null +++ b/src/worker/wordsGeneratorWorker.js @@ -0,0 +1,77 @@ +import randomWords from "random-words"; +// eslint-disable-next-line no-restricted-globals +self.onmessage = function (e) { + const { + ENGLISH_MODE, + COMMON_WORDS, + DEFAULT_DIFFICULTY, + DEFAULT_WORDS_COUNT, + wordsCount, + difficulty, + languageMode, + numberAddOn, + symbolAddOn, + } = e.data; + + const randomIntFromRange = (min, max) => + Math.floor(Math.random() * (max - min + 1)) + min; + const generateRandomNumChras = (min, max) => + String.fromCharCode(randomIntFromRange(min, max) + 48); + const generateRandomSymbolChras = (min, max) => + String.fromCharCode(randomIntFromRange(min, max) + 33); + + const wordsGenerator = ( + wordsCount, + difficulty, + languageMode, + numberAddOn, + symbolAddOn + ) => { + if (languageMode === ENGLISH_MODE) { + if (difficulty === DEFAULT_DIFFICULTY) { + const EnglishWordList = []; + for (let i = 0; i < DEFAULT_WORDS_COUNT; i++) { + const rand = randomIntFromRange(0, 550); + let wordCandidate = COMMON_WORDS[rand].val; + if (numberAddOn) { + wordCandidate = wordCandidate + generateRandomNumChras(1, 2); + } + if (symbolAddOn) { + wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); + } + EnglishWordList.push({ key: wordCandidate, val: wordCandidate }); + } + return EnglishWordList; + } + + // Hard difficulty + const randomWordsGenerated = randomWords({ + exactly: wordsCount, + maxLength: 7, + }); + const words = []; + for (let i = 0; i < wordsCount; i++) { + let wordCandidate = randomWordsGenerated[i]; + if (numberAddOn) { + wordCandidate = wordCandidate + generateRandomNumChras(1, 2); + } + if (symbolAddOn) { + wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); + } + words.push({ key: wordCandidate, val: wordCandidate }); + } + return words; + } + return ["something", "went", "wrong"]; + }; + + const result = wordsGenerator( + wordsCount, + difficulty, + languageMode, + numberAddOn, + symbolAddOn + ); + + postMessage(result); +}; From 0f1fa51698ed529cf6d255a09f1782a5dacfe303 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sat, 17 Aug 2024 23:53:56 +0700 Subject: [PATCH 15/28] feat/make it blazingly fast with web workers move expensive tasks to web worker to prevent UI lag --- .firebase/hosting.YnVpbGQ.cache | 52 +++++++-------- src/components/features/TypeBox/Stats.js | 73 +++++++++++----------- src/components/features/TypeBox/TypeBox.js | 38 ++++++++--- src/worker/calculateRawWpmWorker.js | 10 +++ src/worker/calculateWpmWorker.js | 3 +- src/worker/trackCharsErrorsWorker.js | 9 +++ src/worker/trackHistoryWorker.js | 43 +++++++++++++ 7 files changed, 158 insertions(+), 70 deletions(-) create mode 100644 src/worker/calculateRawWpmWorker.js create mode 100644 src/worker/trackCharsErrorsWorker.js create mode 100644 src/worker/trackHistoryWorker.js diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index dd9fe52..46ff1dd 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,25 +1,27 @@ -robots.txt,1723894396770,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec -manifest.json,1723894396770,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c -index.html,1723894506764,88f92d47b9d678079277fab47a8ef93cc03483a7d668787adcc2d2c47c9f585e -logo.png,1723894396770,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d -asset-manifest.json,1723894506807,e3915da7985732e2d1492a309eeed87f57955959392ffef3987da9fbdf4ec9e0 -logo512.png,1723894396770,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f -logo192.png,1723894396770,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 -favicon.ico,1723894396770,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 -static/media/typeSoft.3e307070c0d43972460c.wav,1723894506797,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 -static/js/main.1c05fc1f.js.LICENSE.txt,1723894506797,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 -static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723894506797,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae -static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723894506797,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e -static/js/181.50cd7f54.chunk.js.map,1723894506807,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c -static/js/181.50cd7f54.chunk.js,1723894506797,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 -static/js/110.443f5259.chunk.js.map,1723894506804,26d05d3583ecd4920bff67743bfdb6d44246091c78fdbaec34ce37b3296d3548 -static/js/110.443f5259.chunk.js,1723894506797,b65c7950a74afd66738cfb80d377ad39536b2558462467b7554f7d731175019a -static/css/main.e6c13ad2.css.map,1723894506797,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 -static/css/main.e6c13ad2.css,1723894506797,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 -static/js/845.d88f7a05.chunk.js,1723894506797,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e -static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723894506797,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 -static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723894506797,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 -static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723894506797,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d -static/js/845.d88f7a05.chunk.js.map,1723894506807,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 -static/js/main.1c05fc1f.js,1723894506797,d98c8eff65ef54b21b23a9dd330abedbf82d961eae2d473bdf6400c8233a044a -static/js/main.1c05fc1f.js.map,1723894506797,9e4de2c25b634c0ae50f09783d8cd7f6f23963c87e5e88ad0f045347d376ee26 +robots.txt,1723906780484,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +manifest.json,1723906780481,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +logo512.png,1723906780481,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +logo192.png,1723906780481,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +logo.png,1723906780481,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +index.html,1723906867931,c1f6850a820fff594d4aad4e0e6721c8828ef883828ad3118c92bf53cc2b4e1b +favicon.ico,1723906780477,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +asset-manifest.json,1723906867967,3f291b7958dd9d5c0821d4ea2d37c822742956da27239bcc99fc1e231ec79fb6 +static/media/typeSoft.3e307070c0d43972460c.wav,1723906867934,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723906867931,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +static/js/main.c882e181.js.LICENSE.txt,1723906867931,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723906867934,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/845.d88f7a05.chunk.js,1723906867934,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/js/215.64dd16d3.chunk.js,1723906867934,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b +static/js/181.50cd7f54.chunk.js.map,1723906867967,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c +static/js/181.50cd7f54.chunk.js,1723906867934,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 +static/js/110.443f5259.chunk.js.map,1723906867967,26d05d3583ecd4920bff67743bfdb6d44246091c78fdbaec34ce37b3296d3548 +static/js/110.443f5259.chunk.js,1723906867934,b65c7950a74afd66738cfb80d377ad39536b2558462467b7554f7d731175019a +static/css/main.e6c13ad2.css.map,1723906867934,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/css/main.e6c13ad2.css,1723906867931,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/js/215.64dd16d3.chunk.js.map,1723906867967,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723906867931,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723906867934,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723906867934,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/845.d88f7a05.chunk.js.map,1723906867967,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/js/main.c882e181.js,1723906867931,b8fcc1fcd7a0ff84e95d78eae02b2851fdd039546e6b333e719c9ec6990f28bb +static/js/main.c882e181.js.map,1723906867964,80e0b11109a58a2277fdc6077d8dea5eee3bc5fa20f1cd41934698efe0af7995 diff --git a/src/components/features/TypeBox/Stats.js b/src/components/features/TypeBox/Stats.js index e6b64ca..923118f 100644 --- a/src/components/features/TypeBox/Stats.js +++ b/src/components/features/TypeBox/Stats.js @@ -20,19 +20,27 @@ const Stats = ({ countDownConstant, statsCharCount, rawKeyStrokes, - wpmKeyStrokes, theme, renderResetButton, setIncorrectCharsCount, incorrectCharsCount, }) => { - const roundedWpm = Math.round( - (wpmKeyStrokes / 5 / Math.max(countDownConstant - countDown, 1)) * 60.0 - ); + const [roundedRawWpm, setRoundedRawWpm] = useState(0); + const roundedWpm = Math.round(wpm); - const roundedRawWpm = Math.round( - (rawKeyStrokes / 5 / Math.max(countDownConstant - countDown, 1)) * 60.0 - ); + useEffect(() => { + const worker = new Worker( + new URL("../../../worker/calculateRawWpmWorker", import.meta.url) + ); + + worker.postMessage({ rawKeyStrokes, countDownConstant, countDown }); + + worker.onmessage = function (e) { + setRoundedRawWpm(e.data); + }; + + return () => worker.terminate(); + }, [rawKeyStrokes, countDownConstant, countDown]); const initialTypingTestHistory = [ { @@ -66,40 +74,33 @@ const Stats = ({ useEffect(() => { if (status === "started" && countDown < countDownConstant) { - let shouldRecord = false; - let increment = 1; - - switch (countDownConstant) { - case 90: - case 60: - shouldRecord = countDown % 5 === 0; - increment = 5; - break; - case 30: - case 15: - shouldRecord = true; - increment = 1; - break; - default: - shouldRecord = true; - increment = 1; - } - - if (shouldRecord) { - const newTime = typingTestHistory.length * increment; + const worker = new Worker( + new URL("../../../worker/trackHistoryWorker", import.meta.url) + ); + worker.postMessage({ + countDown, + countDownConstant, + typingTestHistory, + roundedWpm, + roundedRawWpm, + incorrectCharsCount, + }); + + worker.onmessage = function (e) { + const { newEntry, resetErrors } = e.data; setTypingTestHistory((prevTypingTestHistory) => [ ...prevTypingTestHistory, - { - wpm: roundedWpm, - rawWpm: roundedRawWpm, - time: newTime, - error: incorrectCharsCount, - }, + newEntry, ]); - setIncorrectCharsCount(0); - } + if (resetErrors) { + setIncorrectCharsCount(0); + } + }; + + // Clean up the worker on component unmount + return () => worker.terminate(); } }, [countDown]); diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 375f5e9..e735464 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -709,14 +709,35 @@ const TypeBox = ({ } }; + const charsWorkerRef = useRef(); + + useEffect(() => { + charsWorkerRef.current = new Worker( + new URL("../../../worker/trackCharsErrorsWorker", import.meta.url) + ); + + charsWorkerRef.current.onmessage = (e) => { + if (e.data.type === "increment") { + setIncorrectCharsCount((prev) => prev + 1); + } + }; + + return () => { + charsWorkerRef.current.terminate(); + }; + }, []); + useEffect(() => { if (status !== "started") return; + const word = words[currWordIndex]; - const char = word.split("")[currCharIndex]; - if (char !== currChar && char !== undefined) - return setIncorrectCharsCount((prev) => prev + 1); - }, [currChar, status, currCharIndex]); + charsWorkerRef.current.postMessage({ + word, + currChar, + currCharIndex, + }); + }, [currChar, status, currCharIndex, words, currWordIndex]); const getCharClassName = (wordIdx, charIdx, char, word) => { const keyString = wordIdx + "." + charIdx; @@ -1226,10 +1247,11 @@ const TypeBox = ({ >
{currentWords.map((word, i) => { - const opacityValue = Math.max( - 1 - Math.abs(i - currWordIndex) * 0.1, - 0.1 - ); + const opacityValue = + i >= currWordIndex && i < currWordIndex + 3 + ? 1 + : Math.max(1 - Math.abs(i - currWordIndex) * 0.1, 0.1); + return ( Date: Sun, 18 Aug 2024 00:26:01 +0700 Subject: [PATCH 16/28] feat/add throttling to improve performance --- .firebase/hosting.YnVpbGQ.cache | 60 ++++++++++--------- .../features/WordsCard/WordsCard.js | 47 ++++++++++++--- 2 files changed, 71 insertions(+), 36 deletions(-) diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index 46ff1dd..7e44407 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,27 +1,33 @@ -robots.txt,1723906780484,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec -manifest.json,1723906780481,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c -logo512.png,1723906780481,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f -logo192.png,1723906780481,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 -logo.png,1723906780481,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d -index.html,1723906867931,c1f6850a820fff594d4aad4e0e6721c8828ef883828ad3118c92bf53cc2b4e1b -favicon.ico,1723906780477,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 -asset-manifest.json,1723906867967,3f291b7958dd9d5c0821d4ea2d37c822742956da27239bcc99fc1e231ec79fb6 -static/media/typeSoft.3e307070c0d43972460c.wav,1723906867934,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 -static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723906867931,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae -static/js/main.c882e181.js.LICENSE.txt,1723906867931,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 -static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723906867934,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e -static/js/845.d88f7a05.chunk.js,1723906867934,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e -static/js/215.64dd16d3.chunk.js,1723906867934,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b -static/js/181.50cd7f54.chunk.js.map,1723906867967,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c -static/js/181.50cd7f54.chunk.js,1723906867934,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 -static/js/110.443f5259.chunk.js.map,1723906867967,26d05d3583ecd4920bff67743bfdb6d44246091c78fdbaec34ce37b3296d3548 -static/js/110.443f5259.chunk.js,1723906867934,b65c7950a74afd66738cfb80d377ad39536b2558462467b7554f7d731175019a -static/css/main.e6c13ad2.css.map,1723906867934,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 -static/css/main.e6c13ad2.css,1723906867931,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 -static/js/215.64dd16d3.chunk.js.map,1723906867967,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 -static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723906867931,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 -static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723906867934,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 -static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723906867934,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d -static/js/845.d88f7a05.chunk.js.map,1723906867967,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 -static/js/main.c882e181.js,1723906867931,b8fcc1fcd7a0ff84e95d78eae02b2851fdd039546e6b333e719c9ec6990f28bb -static/js/main.c882e181.js.map,1723906867964,80e0b11109a58a2277fdc6077d8dea5eee3bc5fa20f1cd41934698efe0af7995 +robots.txt,1723913649761,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +manifest.json,1723913649761,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +logo512.png,1723913649761,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +logo192.png,1723913649758,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +index.html,1723913752548,942bf9d2ff8c0d923a26c34448e140e1d7e81c768ec0a8b8551cc255e3b80029 +asset-manifest.json,1723913752588,0fcd3da0228c1b968996fa6193237f62593b0069ff13fbc6abb2ce8310408516 +logo.png,1723913649758,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723913752551,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +static/media/typeSoft.3e307070c0d43972460c.wav,1723913752551,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +favicon.ico,1723913649758,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +static/js/main.70e758bd.js.LICENSE.txt,1723913752551,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/js/911.b91b008e.chunk.js.map,1723913752591,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 +static/js/911.b91b008e.chunk.js,1723913752554,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723913752551,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/514.1d957c45.chunk.js.map,1723913752591,4594e5484d45f44f923fc1d3ebc50bd5a0e8b442cc5ed50d6b1628a665818a1f +static/js/514.1d957c45.chunk.js,1723913752588,04e02477c807ca1b794aa240ee99956d5dc3e1f49a555a92a2f6211c6d7026fc +static/js/268.39edc2bf.chunk.js.map,1723913752591,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 +static/js/845.d88f7a05.chunk.js,1723913752554,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/js/268.39edc2bf.chunk.js,1723913752558,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba +static/js/215.64dd16d3.chunk.js,1723913752554,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b +static/js/181.50cd7f54.chunk.js.map,1723913752591,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c +static/js/181.50cd7f54.chunk.js,1723913752554,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 +static/js/110.abadb285.chunk.js.map,1723913752591,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 +static/js/110.abadb285.chunk.js,1723913752554,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 +static/css/main.e6c13ad2.css.map,1723913752588,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/css/main.e6c13ad2.css,1723913752551,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/js/215.64dd16d3.chunk.js.map,1723913752591,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723913752551,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723913752551,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723913752551,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/845.d88f7a05.chunk.js.map,1723913752591,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/js/main.70e758bd.js,1723913752551,4103832b0fac8fde09ffd4a9db03937ac0e3a523b066bcb8cb17894a0a350735 +static/js/main.70e758bd.js.map,1723913752588,43ecc65b9063a6758d4c9392131c1a8cd341979e88fcf807127a68de4bc1b0dd diff --git a/src/components/features/WordsCard/WordsCard.js b/src/components/features/WordsCard/WordsCard.js index f3ae6c1..3c19808 100644 --- a/src/components/features/WordsCard/WordsCard.js +++ b/src/components/features/WordsCard/WordsCard.js @@ -20,7 +20,7 @@ import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore"; import useSound from "use-sound"; import { SOUND_MAP } from "../sound/sound"; -import VolumeUpIcon from '@mui/icons-material/VolumeUp'; +import VolumeUpIcon from "@mui/icons-material/VolumeUp"; const WordsCard = ({ soundType, soundMode }) => { // set up game loop status state @@ -100,12 +100,35 @@ const WordsCard = ({ soundType, soundMode }) => { } }; + const throttle = (fn, limit) => { + let lastFn; + let lastTime; + return function (...args) { + const context = this; + if (!lastTime) { + fn.apply(context, args); + lastTime = Date.now(); + } else { + clearTimeout(lastFn); + lastFn = setTimeout(() => { + if (Date.now() - lastTime >= limit) { + fn.apply(context, args); + lastTime = Date.now(); + } + }, limit - (Date.now() - lastTime)); + } + }; + }; + const handleInputChange = (e) => { setCurrInput(e.target.value); hiddenInputRef.current.value = e.target.value; e.preventDefault(); }; + // Throttled version of the input change handler + const throttledInputChange = throttle(handleInputChange, 50); + const updateAlphabetSet = (char) => { const newAlphabetSet = new Set(alphabetSet); if (newAlphabetSet.has(char)) { @@ -294,7 +317,7 @@ const WordsCard = ({ soundType, soundMode }) => { if (keyCode === 13 || keyCode === 32) { if (currWord === currInput) { if (keyCode === 32) { - e.preventDefault() + e.preventDefault(); } const nextIndex = index + 1; // to next chapter or back to default @@ -357,7 +380,8 @@ const WordsCard = ({ soundType, soundMode }) => { return ; }; - const audioSource = 'https://dict.youdao.com/dictvoice?audio=' + currWord + '&type=2'; + const audioSource = + "https://dict.youdao.com/dictvoice?audio=" + currWord + "&type=2"; const playAudio = () => { const audio = document.getElementById("hiddenAudio"); @@ -365,7 +389,7 @@ const WordsCard = ({ soundType, soundMode }) => { audio.load(); audio.play(); } - } + }; return (
@@ -386,12 +410,10 @@ const WordsCard = ({ soundType, soundMode }) => { className="hidden-input" ref={hiddenInputRef} onBlur={handleInputBlur} - onChange={handleInputChange} + onChange={throttledInputChange} onKeyDown={(e) => handleKeyDown(e)} > -
- {currMeaning} -
+
{currMeaning}
{
-
{"Chapter " + currChapter.toUpperCase() + ": "} {index + 1} /{" "} From c265c20b98bfbbe83a82eca256f4d05afdbc2ba9 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Sun, 18 Aug 2024 00:32:32 +0700 Subject: [PATCH 17/28] remove unnecessary trottle --- .firebase/hosting.YnVpbGQ.cache | 66 +++++++++---------- .../features/WordsCard/WordsCard.js | 25 +------ 2 files changed, 34 insertions(+), 57 deletions(-) diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index 7e44407..b08731b 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,33 +1,33 @@ -robots.txt,1723913649761,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec -manifest.json,1723913649761,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c -logo512.png,1723913649761,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f -logo192.png,1723913649758,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 -index.html,1723913752548,942bf9d2ff8c0d923a26c34448e140e1d7e81c768ec0a8b8551cc255e3b80029 -asset-manifest.json,1723913752588,0fcd3da0228c1b968996fa6193237f62593b0069ff13fbc6abb2ce8310408516 -logo.png,1723913649758,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d -static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723913752551,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae -static/media/typeSoft.3e307070c0d43972460c.wav,1723913752551,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 -favicon.ico,1723913649758,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 -static/js/main.70e758bd.js.LICENSE.txt,1723913752551,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 -static/js/911.b91b008e.chunk.js.map,1723913752591,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 -static/js/911.b91b008e.chunk.js,1723913752554,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f -static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723913752551,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e -static/js/514.1d957c45.chunk.js.map,1723913752591,4594e5484d45f44f923fc1d3ebc50bd5a0e8b442cc5ed50d6b1628a665818a1f -static/js/514.1d957c45.chunk.js,1723913752588,04e02477c807ca1b794aa240ee99956d5dc3e1f49a555a92a2f6211c6d7026fc -static/js/268.39edc2bf.chunk.js.map,1723913752591,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 -static/js/845.d88f7a05.chunk.js,1723913752554,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e -static/js/268.39edc2bf.chunk.js,1723913752558,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba -static/js/215.64dd16d3.chunk.js,1723913752554,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b -static/js/181.50cd7f54.chunk.js.map,1723913752591,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c -static/js/181.50cd7f54.chunk.js,1723913752554,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 -static/js/110.abadb285.chunk.js.map,1723913752591,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 -static/js/110.abadb285.chunk.js,1723913752554,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 -static/css/main.e6c13ad2.css.map,1723913752588,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 -static/css/main.e6c13ad2.css,1723913752551,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 -static/js/215.64dd16d3.chunk.js.map,1723913752591,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 -static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723913752551,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 -static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723913752551,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 -static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723913752551,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d -static/js/845.d88f7a05.chunk.js.map,1723913752591,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 -static/js/main.70e758bd.js,1723913752551,4103832b0fac8fde09ffd4a9db03937ac0e3a523b066bcb8cb17894a0a350735 -static/js/main.70e758bd.js.map,1723913752588,43ecc65b9063a6758d4c9392131c1a8cd341979e88fcf807127a68de4bc1b0dd +robots.txt,1723915577058,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +manifest.json,1723915577058,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +logo512.png,1723915577058,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +logo192.png,1723915577058,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +index.html,1723915653121,77bdbdb4f2b68e456da7246235c43d74b539cf4b2ecda611eb7fdc48c514a5fc +logo.png,1723915577058,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +asset-manifest.json,1723915653155,896b837f3b9b709aa7412887935fd5e2577cdd2210601f812782f7cf17ab61b9 +favicon.ico,1723915577058,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +static/media/typeSoft.3e307070c0d43972460c.wav,1723915653125,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723915653125,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +static/js/main.9d0359a8.js.LICENSE.txt,1723915653125,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/js/911.b91b008e.chunk.js.map,1723915653158,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 +static/js/911.b91b008e.chunk.js,1723915653125,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723915653125,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/845.d88f7a05.chunk.js,1723915653125,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/js/514.1d957c45.chunk.js.map,1723915653158,4594e5484d45f44f923fc1d3ebc50bd5a0e8b442cc5ed50d6b1628a665818a1f +static/js/514.1d957c45.chunk.js,1723915653155,04e02477c807ca1b794aa240ee99956d5dc3e1f49a555a92a2f6211c6d7026fc +static/js/268.39edc2bf.chunk.js.map,1723915653158,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 +static/js/268.39edc2bf.chunk.js,1723915653125,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba +static/js/215.64dd16d3.chunk.js,1723915653125,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b +static/js/181.50cd7f54.chunk.js.map,1723915653158,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c +static/js/181.50cd7f54.chunk.js,1723915653125,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 +static/js/110.abadb285.chunk.js.map,1723915653158,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 +static/js/110.abadb285.chunk.js,1723915653125,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 +static/css/main.e6c13ad2.css.map,1723915653155,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/css/main.e6c13ad2.css,1723915653125,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723915653125,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/js/215.64dd16d3.chunk.js.map,1723915653158,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723915653125,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/js/845.d88f7a05.chunk.js.map,1723915653158,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723915653125,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/main.9d0359a8.js,1723915653125,704566c8121f8c5012884ad5a1ca03500ef365ab8576281e551b92187804cd75 +static/js/main.9d0359a8.js.map,1723915653155,eeeb66e0b580d2f3cf662710e80f2585a59e12e0df5e641e3e6b74f1bdabeafc diff --git a/src/components/features/WordsCard/WordsCard.js b/src/components/features/WordsCard/WordsCard.js index 3c19808..f9a4714 100644 --- a/src/components/features/WordsCard/WordsCard.js +++ b/src/components/features/WordsCard/WordsCard.js @@ -100,35 +100,12 @@ const WordsCard = ({ soundType, soundMode }) => { } }; - const throttle = (fn, limit) => { - let lastFn; - let lastTime; - return function (...args) { - const context = this; - if (!lastTime) { - fn.apply(context, args); - lastTime = Date.now(); - } else { - clearTimeout(lastFn); - lastFn = setTimeout(() => { - if (Date.now() - lastTime >= limit) { - fn.apply(context, args); - lastTime = Date.now(); - } - }, limit - (Date.now() - lastTime)); - } - }; - }; - const handleInputChange = (e) => { setCurrInput(e.target.value); hiddenInputRef.current.value = e.target.value; e.preventDefault(); }; - // Throttled version of the input change handler - const throttledInputChange = throttle(handleInputChange, 50); - const updateAlphabetSet = (char) => { const newAlphabetSet = new Set(alphabetSet); if (newAlphabetSet.has(char)) { @@ -410,7 +387,7 @@ const WordsCard = ({ soundType, soundMode }) => { className="hidden-input" ref={hiddenInputRef} onBlur={handleInputBlur} - onChange={throttledInputChange} + onChange={handleInputChange} onKeyDown={(e) => handleKeyDown(e)} >
{currMeaning}
From 59c63b00e121bcd81a20b5ccd94d460727a06bfd Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Mon, 19 Aug 2024 15:01:18 +0700 Subject: [PATCH 18/28] give indentation --- .firebase/hosting.YnVpbGQ.cache | 66 +- src/style/global.js | 1454 +++++++++++++++---------------- 2 files changed, 760 insertions(+), 760 deletions(-) diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index b08731b..a7e9aa2 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,33 +1,33 @@ -robots.txt,1723915577058,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec -manifest.json,1723915577058,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c -logo512.png,1723915577058,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f -logo192.png,1723915577058,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 -index.html,1723915653121,77bdbdb4f2b68e456da7246235c43d74b539cf4b2ecda611eb7fdc48c514a5fc -logo.png,1723915577058,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d -asset-manifest.json,1723915653155,896b837f3b9b709aa7412887935fd5e2577cdd2210601f812782f7cf17ab61b9 -favicon.ico,1723915577058,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 -static/media/typeSoft.3e307070c0d43972460c.wav,1723915653125,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 -static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723915653125,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae -static/js/main.9d0359a8.js.LICENSE.txt,1723915653125,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 -static/js/911.b91b008e.chunk.js.map,1723915653158,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 -static/js/911.b91b008e.chunk.js,1723915653125,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f -static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723915653125,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e -static/js/845.d88f7a05.chunk.js,1723915653125,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e -static/js/514.1d957c45.chunk.js.map,1723915653158,4594e5484d45f44f923fc1d3ebc50bd5a0e8b442cc5ed50d6b1628a665818a1f -static/js/514.1d957c45.chunk.js,1723915653155,04e02477c807ca1b794aa240ee99956d5dc3e1f49a555a92a2f6211c6d7026fc -static/js/268.39edc2bf.chunk.js.map,1723915653158,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 -static/js/268.39edc2bf.chunk.js,1723915653125,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba -static/js/215.64dd16d3.chunk.js,1723915653125,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b -static/js/181.50cd7f54.chunk.js.map,1723915653158,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c -static/js/181.50cd7f54.chunk.js,1723915653125,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 -static/js/110.abadb285.chunk.js.map,1723915653158,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 -static/js/110.abadb285.chunk.js,1723915653125,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 -static/css/main.e6c13ad2.css.map,1723915653155,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 -static/css/main.e6c13ad2.css,1723915653125,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 -static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723915653125,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 -static/js/215.64dd16d3.chunk.js.map,1723915653158,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 -static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723915653125,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 -static/js/845.d88f7a05.chunk.js.map,1723915653158,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 -static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723915653125,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d -static/js/main.9d0359a8.js,1723915653125,704566c8121f8c5012884ad5a1ca03500ef365ab8576281e551b92187804cd75 -static/js/main.9d0359a8.js.map,1723915653155,eeeb66e0b580d2f3cf662710e80f2585a59e12e0df5e641e3e6b74f1bdabeafc +manifest.json,1723915964268,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +robots.txt,1723915964271,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +logo512.png,1723915964268,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +logo.png,1723915964268,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +logo192.png,1723915964268,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +favicon.ico,1723915964268,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +asset-manifest.json,1723916035851,0fcd3da0228c1b968996fa6193237f62593b0069ff13fbc6abb2ce8310408516 +index.html,1723916035808,942bf9d2ff8c0d923a26c34448e140e1d7e81c768ec0a8b8551cc255e3b80029 +static/media/typeSoft.3e307070c0d43972460c.wav,1723916035811,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723916035811,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +static/js/main.70e758bd.js.LICENSE.txt,1723916035815,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/js/911.b91b008e.chunk.js.map,1723916035855,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 +static/js/911.b91b008e.chunk.js,1723916035815,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f +static/js/845.d88f7a05.chunk.js,1723916035815,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/js/514.1d957c45.chunk.js.map,1723916035858,4594e5484d45f44f923fc1d3ebc50bd5a0e8b442cc5ed50d6b1628a665818a1f +static/js/514.1d957c45.chunk.js,1723916035851,04e02477c807ca1b794aa240ee99956d5dc3e1f49a555a92a2f6211c6d7026fc +static/js/268.39edc2bf.chunk.js.map,1723916035858,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 +static/js/268.39edc2bf.chunk.js,1723916035818,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723916035815,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/215.64dd16d3.chunk.js,1723916035815,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b +static/js/181.50cd7f54.chunk.js.map,1723916035855,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c +static/js/181.50cd7f54.chunk.js,1723916035815,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 +static/js/110.abadb285.chunk.js.map,1723916035855,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 +static/js/110.abadb285.chunk.js,1723916035815,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 +static/css/main.e6c13ad2.css.map,1723916035851,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/css/main.e6c13ad2.css,1723916035815,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/js/215.64dd16d3.chunk.js.map,1723916035858,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723916035811,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723916035815,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723916035811,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/845.d88f7a05.chunk.js.map,1723916035858,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/js/main.70e758bd.js,1723916035815,4103832b0fac8fde09ffd4a9db03937ac0e3a523b066bcb8cb17894a0a350735 +static/js/main.70e758bd.js.map,1723916035855,00ed1893464f348a6e6c04f455b58d11ba4ed08caa5d0ee9e581b544a9a8f167 diff --git a/src/style/global.js b/src/style/global.js index a101f18..00cbeb6 100644 --- a/src/style/global.js +++ b/src/style/global.js @@ -6,757 +6,757 @@ export const GlobalStyles = createGlobalStyle` *::before { box-sizing: border-box; } - body { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; - background: ${({ theme }) => theme.background}; - color: ${({ theme }) => theme.text}; - padding: 0; - margin: 0; - font-family: ${({ theme }) => theme.fontFamily}; - transition: all 0.25s linear; - text-shadow: ${({ theme }) => theme.textShadow}; - } - .canvas { - align-items: center; - display: grid; - gap: 1rem; - grid-auto-flow: row; - grid-template-rows: auto 1fr auto; - min-height: 100vh; - width: 100vw; - z-index: 1; - padding: 1rem; - transition: padding-top .125s; - } +body { +display: flex; +flex-direction: column; +align-items: center; +justify-content: center; +height: 100%; +width: 100%; +background: ${({ theme }) => theme.background}; +color: ${({ theme }) => theme.text}; +padding: 0; +margin: 0; +font-family: ${({ theme }) => theme.fontFamily}; +transition: all 0.25s linear; +text-shadow: ${({ theme }) => theme.textShadow}; +} +.canvas { +align-items: center; +display: grid; +gap: 1rem; +grid-auto-flow: row; +grid-template-rows: auto 1fr auto; +min-height: 100vh; +width: 100vw; +z-index: 1; +padding: 1rem; +transition: padding-top .125s; +} .fixed-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); /* Dark background with opacity */ - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; /* Ensure the overlay is on top */ +position: fixed; +top: 0; +left: 0; +width: 100%; +height: 100%; +background: rgba(0, 0, 0, 0.5); /* Dark background with opacity */ +display: flex; +align-items: center; +justify-content: center; +z-index: 9999; /* Ensure the overlay is on top */ } .modal-content { - background: ${({ theme }) => theme.background}; - padding: 40px; /* Increased padding */ - border-radius: 8px; - position: relative; /* To position the close button */ - width: 80%; /* Increased width */ - max-width: 600px; /* Max width to keep it from getting too large */ - height: auto; /* Allow height to grow with content */ +background: ${({ theme }) => theme.background}; +padding: 40px; /* Increased padding */ +border-radius: 8px; +position: relative; /* To position the close button */ +width: 80%; /* Increased width */ +max-width: 600px; /* Max width to keep it from getting too large */ +height: auto; /* Allow height to grow with content */ } .close-button { - color: ${({ theme }) => theme.textTypeBox}; +color: ${({ theme }) => theme.textTypeBox}; } .modal-title { - margin-bottom: 20px; /* Add space below title */ +margin-bottom: 20px; /* Add space below title */ } .modal-description { - margin-bottom: 20px; /* Add space below description */ +margin-bottom: 20px; /* Add space below description */ } .modal-icons { - margin-top: 20px; /* Add space above icons */ -} - .dynamicBackground { - heigh: 100%; - width: 100%; - z-index: -999; - position: fixed; - filter: grayscale(30%); - } - .header { - position: relative; - display: block; - align-items: center; - justify-content: center; - padding-bottom: 2%; - top: 0; - left:0; - width: 100%; - text-align: center; - z-index: 999; - } - .bottom-info { - color: ${({ theme }) => theme.title}; - margin: 4px; - } - small { - display: block; - } - button { - display: block; - } - h1 { - color: ${({ theme }) => theme.title}; - opacity: 0.9; - margin-top: 10px; - margin-bottom: 10px; - } - h3{ - margin-right: 10px; - } - h4{ - margin-right: 10px; - opacity: 0.7; - } - .bottomBar { - z-index: 999; - } +margin-top: 20px; /* Add space above icons */ +} +.dynamicBackground { +heigh: 100%; +width: 100%; +z-index: -999; +position: fixed; +filter: grayscale(30%); +} +.header { +position: relative; +display: block; +align-items: center; +justify-content: center; +padding-bottom: 2%; +top: 0; +left:0; +width: 100%; +text-align: center; +z-index: 999; +} +.bottom-info { +color: ${({ theme }) => theme.title}; +margin: 4px; +} +small { +display: block; +} +button { +display: block; +} +h1 { +color: ${({ theme }) => theme.title}; +opacity: 0.9; +margin-top: 10px; +margin-bottom: 10px; +} +h3{ +margin-right: 10px; +} +h4{ +margin-right: 10px; +opacity: 0.7; +} +.bottomBar { +z-index: 999; +} - .stats-overlay { - position: fixed; - background: ${({ theme }) => theme.background}; - inset: 0; - z-index: 99; - padding-inline: 1rem; - } +.stats-overlay { +position: fixed; +background: ${({ theme }) => theme.background}; +inset: 0; +z-index: 99; +padding-inline: 1rem; +} - .stats-chart { - position: absolute; - background: transparent; - top: 50%; - width: 100%; - max-width: 1000px; - left: 50%; - transform: translate(-50%, -50%); - display: flex; - flex-direction: column; - gap: 20px; - } +.stats-chart { +position: absolute; +background: transparent; +top: 50%; +width: 100%; +max-width: 1000px; +left: 50%; +transform: translate(-50%, -50%); +display: flex; +flex-direction: column; +gap: 20px; +} - .custom-tooltip { - position: relative; - } +.custom-tooltip { +position: relative; +} - .custom-tooltip::before { - content: ""; - position: absolute; - width: 100%; - height: 100%; - inset: 0; - background: ${({ theme }) => theme.background}; - z-index: -1; - border: 1px solid ${({ theme }) => theme.textTypeBox}; - opacity: .9; - } +.custom-tooltip::before { +content: ""; +position: absolute; +width: 100%; +height: 100%; +inset: 0; +background: ${({ theme }) => theme.background}; +z-index: -1; +border: 1px solid ${({ theme }) => theme.textTypeBox}; +opacity: .9; +} - .stats-header { - width: 100%; - display: grid; - grid-template-columns: auto 1fr; - gap: 16px; - } +.stats-header { +width: 100%; +display: grid; +grid-template-columns: auto 1fr; +gap: 16px; +} - .stats { - display: block; - max-width: 1000px; - margin-top: 50px; - margin-bottom: 20px; - margin-left: auto; - margin-right: auto; - color: ${({ theme }) => theme.stats}; - bottom: 10%; - } +.stats { +display: block; +max-width: 1000px; +margin-top: 50px; +margin-bottom: 20px; +margin-left: auto; +margin-right: auto; +color: ${({ theme }) => theme.stats}; +bottom: 10%; +} - .stats-footer { - display: flex; - justify-content: space-between; - } - .wordscard-UI{ - display: block; - max-width: 1000px; - margin-top: 150px; - margin-bottom: 20px; - margin-left: auto; - margin-right: auto; - bottom: 10%; - } - .wordscard-UI-info{ - margin-top: 30px; - margin-bottom: 20px; - margin-left: auto; - margin-right: auto; - color: ${({ theme }) => theme.textTypeBox}; - bottom: 10%; - } - .keyboard-stats { - display: flex; - max-width: 1000px; - margin-top: 50px; - margin-bottom: 20px; - margin-left: auto; - margin-right: auto; - color: ${({ theme }) => theme.stats}; - bottom: 10%; - justify-content: center; - text-align: center; - } - .sub-header { - color: ${({ theme }) => theme.textTypeBox}; - opacity: 0.5; - border-right: 2px solid; - animation: blinkingCursor 2s infinite;; - @keyframes blinkingCursor{ - 0% { border-right-color: ${({ theme }) => theme.stats};} - 25% { border-right-color: transparent;} - 50% { border-right-color: ${({ theme }) => theme.stats};} - 75% {border-right-color: transparent;} - 100% {border-right-color: ${({ theme }) => theme.stats};} - } - } - .type-box { - display: block; - max-width: 1000px; - height: 140px; - overflow: hidden; - margin-left: auto; - margin-right: auto; - position: relative; - top: 10%; - @media only screen - and (min-device-width: 375px) - and (max-device-width: 812px) - and (-webkit-min-device-pixel-ratio: 3) { - top:200px; - width: 60%; - } - } - .type-box-chinese { - display: block; - max-width: 1000px; - height: 240px; - overflow: hidden; - margin-left: auto; - margin-right: auto; - position: relative - top: 10%; - @media only screen - and (min-device-width: 375px) - and (max-device-width: 812px) - and (-webkit-min-device-pixel-ratio: 3) { - top:200px; - width: 60%; - } - } - .words{ - color: ${({ theme }) => theme.textTypeBox}; - font-size: 28px; - display: flex; - flex-wrap: wrap; - width: 100%; - align-content: center; - user-select: none; - } - .word{ - margin: 5px 5px; - display: flex; - padding-right: 2px; - border-bottom: 1px solid transparent; - border-top: 1px solid transparent; - scroll-margin: 4px; - } - .active-word{ - animation: blinkingBackground 2s infinite; - border-top: 1px solid transparent; - border-bottom: 1px solid; - @keyframes blinkingBackground{ - 0% { border-bottom-color: ${({ theme }) => theme.stats};} - 25% { border-bottom-color: ${({ theme }) => theme.textTypeBox};} - 50% { border-bottom-color: ${({ theme }) => theme.stats};} - 75% {border-bottom-color: ${({ theme }) => theme.textTypeBox};} - 100% {border-bottom-color: ${({ theme }) => theme.stats};} - }; - scroll-margin: 4px; - } - .active-word-no-pulse{ - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - scroll-margin: 4px; - } - .error-word{ - border-bottom: 1px solid red; - scroll-margin: 4px; - } - .char{ - border-left: 1px solid transparent; - border-right: 1px solid transparent; - } - .correct-char{ - border-left: 1px solid transparent; - border-right: 1px solid transparent; - color: ${({ theme }) => theme.text}; - } - .error-char{ - border-left: 1px solid transparent; - border-right: 1px solid transparent; - color: red; - } - .caret-char-left{ - border-left: 1px solid ${({ theme }) => theme.stats}; - border-right: 1px solid transparent; - } - .caret-char-left-start{ - border-left: 1px solid; - border-right: 1px solid transparent; - animation: blinkingCaretLeft 2s infinite; - animation-timing-function: ease; - @keyframes blinkingCaretLeft{ - 0% { border-left-color: ${({ theme }) => theme.stats};} - 25% { border-left-color: ${({ theme }) => theme.textTypeBox};} - 50% { border-left-color: ${({ theme }) => theme.stats};} - 75% { border-left-color: ${({ theme }) => theme.textTypeBox};} - 100% { border-left-color: ${({ theme }) => theme.stats};} - } - } - .caret-char-right{ - border-right: 1px solid ${({ theme }) => theme.stats}; - border-left: 1x solid transparent; - } - .caret-char-right-correct{ - color: ${({ theme }) => theme.text}; - border-right: 1px solid ${({ theme }) => theme.stats}; - border-left: 1px solid transparent; - } - .caret-char-right-error{ - color: red; - border-right: 1px solid ${({ theme }) => theme.stats}; - border-left: 1px solid transparent; - } - .caret-extra-char-right-error{ - color: red; - border-right: 1px solid ${({ theme }) => theme.stats}; - border-left: 1px solid transparent; - } +.stats-footer { +display: flex; +justify-content: space-between; +} +.wordscard-UI{ +display: block; +max-width: 1000px; +margin-top: 150px; +margin-bottom: 20px; +margin-left: auto; +margin-right: auto; +bottom: 10%; +} +.wordscard-UI-info{ +margin-top: 30px; +margin-bottom: 20px; +margin-left: auto; +margin-right: auto; +color: ${({ theme }) => theme.textTypeBox}; +bottom: 10%; +} +.keyboard-stats { +display: flex; +max-width: 1000px; +margin-top: 50px; +margin-bottom: 20px; +margin-left: auto; +margin-right: auto; +color: ${({ theme }) => theme.stats}; +bottom: 10%; +justify-content: center; +text-align: center; +} +.sub-header { +color: ${({ theme }) => theme.textTypeBox}; +opacity: 0.5; +border-right: 2px solid; +animation: blinkingCursor 2s infinite;; +@keyframes blinkingCursor{ +0% { border-right-color: ${({ theme }) => theme.stats};} +25% { border-right-color: transparent;} +50% { border-right-color: ${({ theme }) => theme.stats};} +75% {border-right-color: transparent;} +100% {border-right-color: ${({ theme }) => theme.stats};} +} +} +.type-box { +display: block; +max-width: 1000px; +height: 140px; +overflow: hidden; +margin-left: auto; +margin-right: auto; +position: relative; +top: 10%; +@media only screen +and (min-device-width: 375px) +and (max-device-width: 812px) +and (-webkit-min-device-pixel-ratio: 3) { +top:200px; +width: 60%; +} +} +.type-box-chinese { +display: block; +max-width: 1000px; +height: 240px; +overflow: hidden; +margin-left: auto; +margin-right: auto; +position: relative +top: 10%; +@media only screen +and (min-device-width: 375px) +and (max-device-width: 812px) +and (-webkit-min-device-pixel-ratio: 3) { +top:200px; +width: 60%; +} +} +.words{ +color: ${({ theme }) => theme.textTypeBox}; +font-size: 28px; +display: flex; +flex-wrap: wrap; +width: 100%; +align-content: center; +user-select: none; +} +.word{ +margin: 5px 5px; +display: flex; +padding-right: 2px; +border-bottom: 1px solid transparent; +border-top: 1px solid transparent; +scroll-margin: 4px; +} +.active-word{ +animation: blinkingBackground 2s infinite; +border-top: 1px solid transparent; +border-bottom: 1px solid; +@keyframes blinkingBackground{ +0% { border-bottom-color: ${({ theme }) => theme.stats};} +25% { border-bottom-color: ${({ theme }) => theme.textTypeBox};} +50% { border-bottom-color: ${({ theme }) => theme.stats};} +75% {border-bottom-color: ${({ theme }) => theme.textTypeBox};} +100% {border-bottom-color: ${({ theme }) => theme.stats};} +}; +scroll-margin: 4px; +} +.active-word-no-pulse{ +border-top: 1px solid transparent; +border-bottom: 1px solid transparent; +scroll-margin: 4px; +} +.error-word{ +border-bottom: 1px solid red; +scroll-margin: 4px; +} +.char{ +border-left: 1px solid transparent; +border-right: 1px solid transparent; +} +.correct-char{ +border-left: 1px solid transparent; +border-right: 1px solid transparent; +color: ${({ theme }) => theme.text}; +} +.error-char{ +border-left: 1px solid transparent; +border-right: 1px solid transparent; +color: red; +} +.caret-char-left{ +border-left: 1px solid ${({ theme }) => theme.stats}; +border-right: 1px solid transparent; +} +.caret-char-left-start{ +border-left: 1px solid; +border-right: 1px solid transparent; +animation: blinkingCaretLeft 2s infinite; +animation-timing-function: ease; +@keyframes blinkingCaretLeft{ +0% { border-left-color: ${({ theme }) => theme.stats};} +25% { border-left-color: ${({ theme }) => theme.textTypeBox};} +50% { border-left-color: ${({ theme }) => theme.stats};} +75% { border-left-color: ${({ theme }) => theme.textTypeBox};} +100% { border-left-color: ${({ theme }) => theme.stats};} +} +} +.caret-char-right{ +border-right: 1px solid ${({ theme }) => theme.stats}; +border-left: 1x solid transparent; +} +.caret-char-right-correct{ +color: ${({ theme }) => theme.text}; +border-right: 1px solid ${({ theme }) => theme.stats}; +border-left: 1px solid transparent; +} +.caret-char-right-error{ +color: red; +border-right: 1px solid ${({ theme }) => theme.stats}; +border-left: 1px solid transparent; +} +.caret-extra-char-right-error{ +color: red; +border-right: 1px solid ${({ theme }) => theme.stats}; +border-left: 1px solid transparent; +} - .hidden-input{ - opacity:0; - filter:alpha(opacity=0); - } - .select { - color: ${({ theme }) => theme.text}; - background: ${({ theme }) => theme.background}; - border: none; - min-width: 5%; - } - .restart-button{ - margin-left: auto; - margin-right: auto; - width: 8em - } - .restart-button button:hover{ - transform:scale(1.18); - transition:0.3s; - } - .alert{ - opacity: 0.3; - background-image: ${({ theme }) => theme.gradient}; - } - .correct-char-stats{ - color: ${({ theme }) => theme.text}; - } - .incorrect-char-stats{ - color: red; - } - .missing-char-stats{ - color: ${({ theme }) => theme.textTypeBox}; - } - .speedbar{ - opacity: 0.3; - color: ${({ theme }) => theme.stats}; - } - .active-button{ - color: ${({ theme }) => theme.stats}; - } - .inactive-button{ - color: ${({ theme }) => theme.textTypeBox}; - } - .zen-button{ - color: ${({ theme }) => theme.stats}; - } - .zen-button-deactive{ - color: ${({ theme }) => theme.textTypeBox}; - } - .support-me{ - color : #FF4081; - animation: blinkingColor 10s infinite; - @keyframes blinkingColor{ - 0% { color: #F48FB1;} - 25% { color: #FF4081;} - 50% { color: #F48FB1;} - 75% {color: #FF4081;} - 100% {color: #F48FB1;} - } - } - .support-me-image{ - height: 75%; - width: 75%; - display: block; - margin-left: auto; - margin-right: auto; - margin-top: 8px; - margin-bottom: 8px; - border-radius: 16px; - } - .menu-separater{ - color: ${({ theme }) => theme.textTypeBox}; - background-color: none; - font-size: 16px; - } - .chinese-word{ - margin-left: 10px; - margin-right: 10px; - margin-bottom: 10px; - display: flex; - padding-right: 2px; - border-bottom: 1px solid transparent; - border-top: 1px solid transparent; - } - .chinese-word-key{ - margin: 4px 4px; - color: ${({ theme }) => theme.textTypeBox}; - background-color: none; - display: flex; - justify-content: center; - font-size: 20px; - scroll-margin: 4px; - text-align: center; - } - .error-chinese{ - color: red; - } - .active-chinese{ - color: ${({ theme }) => theme.stats}; - } - .dialog{ - background: ${({ theme }) => theme.background}; - } - .key-type{ - background: ${({ theme }) => theme.textTypeBox}; - color: ${({ theme }) => theme.stats}; - border-radius: 4px; - } - .key-note{ - color: ${({ theme }) => theme.stats}; - background: transparent; - } - .novelty-container{ - width: 80%; - height: 100%; - margin-left: auto; - margin-right: auto; - position: relative; - display: block; - } - .textarea{ - color: ${({ theme }) => theme.textTypeBox}; - font-size: 28px; - background: transparent; - border: none; - caret-color: ${({ theme }) => theme.stats}; - font-family: ${({ theme }) => theme.fontFamily}; - overflow: auto; - resize: none; - width: 100%; - height: 70vh; - margin-left: auto; - margin-right: auto; - position: relative; - outline: none; - border-radius: 4px; - @media only screen - and (min-device-width: 375px) - and (max-device-width: 812px) - and (-webkit-min-device-pixel-ratio: 3) { - top:200px; - width: 60%; - } - } - .active-game-mode-button{ - color: ${({ theme }) => theme.stats}; - font-size: 16px; - } - .inactive-game-mode-button{ - color: ${({ theme }) => theme.textTypeBox}; - font-size: 16px; - } - .error-sentence-char{ - color: red; - } - .error-sentence-space-char{ - border-bottom: 1px solid red; - } - .wordcard-error-char-space-char{ - border-bottom: 1px solid red; - white-space:pre; - padding-right: 4px; - } - .wordcard-error-char{ - color: red; - padding-right: 4px; - } - .wordcard-char{ - color: ${({ theme }) => theme.textTypeBox}; - padding-right: 4px; - } - .correct-wordcard-char{ - color: ${({ theme }) => theme.text}; - padding-right: 4px; - } - .sentence-char{ - color: ${({ theme }) => theme.textTypeBox}; - } - .correct-sentence-char{ - color: ${({ theme }) => theme.text}; - } - .sentence-input-field{ - color: ${({ theme }) => theme.textTypeBox}; - font-size: 28px; - background: transparent; - border: none; - caret-color: ${({ theme }) => theme.stats}; - outline: none; - padding: 0; - font-family: ${({ theme }) => theme.fontFamily}; - } - .sentence-display-field{ - font-size: 28px; - } - .wordcard-word-display-field{ - font-size: 64px; - margin: 40px; - } - .wordcard-meaning-display-field{ - font-size: 20px; - margin-top: 40px; - margin-bottom: 10px; - } - .next-sentence-display{ - font-family: ${({ theme }) => theme.fontFamily}; - color: ${({ theme }) => theme.textTypeBox}; - display: block; - margin-top: 10px; - font-size: 16px; - } - .type-box-sentence { - display: block; - max-width: 1000px; - height: 240px; - overflow: hidden; - margin-left: auto; - margin-right: auto; - position: relative - top: 10%; - @media only screen - and (min-device-width: 375px) - and (max-device-width: 812px) - and (-webkit-min-device-pixel-ratio: 3) { - top:200px; - width: 60%; - } - } +.hidden-input{ +opacity:0; +filter:alpha(opacity=0); +} +.select { +color: ${({ theme }) => theme.text}; +background: ${({ theme }) => theme.background}; +border: none; +min-width: 5%; +} +.restart-button{ +margin-left: auto; +margin-right: auto; +width: 8em +} +.restart-button button:hover{ +transform:scale(1.18); +transition:0.3s; +} +.alert{ +opacity: 0.3; +background-image: ${({ theme }) => theme.gradient}; +} +.correct-char-stats{ +color: ${({ theme }) => theme.text}; +} +.incorrect-char-stats{ +color: red; +} +.missing-char-stats{ +color: ${({ theme }) => theme.textTypeBox}; +} +.speedbar{ +opacity: 0.3; +color: ${({ theme }) => theme.stats}; +} +.active-button{ +color: ${({ theme }) => theme.stats}; +} +.inactive-button{ +color: ${({ theme }) => theme.textTypeBox}; +} +.zen-button{ +color: ${({ theme }) => theme.stats}; +} +.zen-button-deactive{ +color: ${({ theme }) => theme.textTypeBox}; +} +.support-me{ +color : #FF4081; +animation: blinkingColor 10s infinite; +@keyframes blinkingColor{ +0% { color: #F48FB1;} +25% { color: #FF4081;} +50% { color: #F48FB1;} +75% {color: #FF4081;} +100% {color: #F48FB1;} +} +} +.support-me-image{ +height: 75%; +width: 75%; +display: block; +margin-left: auto; +margin-right: auto; +margin-top: 8px; +margin-bottom: 8px; +border-radius: 16px; +} +.menu-separater{ +color: ${({ theme }) => theme.textTypeBox}; +background-color: none; +font-size: 16px; +} +.chinese-word{ +margin-left: 10px; +margin-right: 10px; +margin-bottom: 10px; +display: flex; +padding-right: 2px; +border-bottom: 1px solid transparent; +border-top: 1px solid transparent; +} +.chinese-word-key{ +margin: 4px 4px; +color: ${({ theme }) => theme.textTypeBox}; +background-color: none; +display: flex; +justify-content: center; +font-size: 20px; +scroll-margin: 4px; +text-align: center; +} +.error-chinese{ +color: red; +} +.active-chinese{ +color: ${({ theme }) => theme.stats}; +} +.dialog{ +background: ${({ theme }) => theme.background}; +} +.key-type{ +background: ${({ theme }) => theme.textTypeBox}; +color: ${({ theme }) => theme.stats}; +border-radius: 4px; +} +.key-note{ +color: ${({ theme }) => theme.stats}; +background: transparent; +} +.novelty-container{ +width: 80%; +height: 100%; +margin-left: auto; +margin-right: auto; +position: relative; +display: block; +} +.textarea{ +color: ${({ theme }) => theme.textTypeBox}; +font-size: 28px; +background: transparent; +border: none; +caret-color: ${({ theme }) => theme.stats}; +font-family: ${({ theme }) => theme.fontFamily}; +overflow: auto; +resize: none; +width: 100%; +height: 70vh; +margin-left: auto; +margin-right: auto; +position: relative; +outline: none; +border-radius: 4px; +@media only screen +and (min-device-width: 375px) +and (max-device-width: 812px) +and (-webkit-min-device-pixel-ratio: 3) { +top:200px; +width: 60%; +} +} +.active-game-mode-button{ +color: ${({ theme }) => theme.stats}; +font-size: 16px; +} +.inactive-game-mode-button{ +color: ${({ theme }) => theme.textTypeBox}; +font-size: 16px; +} +.error-sentence-char{ +color: red; +} +.error-sentence-space-char{ +border-bottom: 1px solid red; +} +.wordcard-error-char-space-char{ +border-bottom: 1px solid red; +white-space:pre; +padding-right: 4px; +} +.wordcard-error-char{ +color: red; +padding-right: 4px; +} +.wordcard-char{ +color: ${({ theme }) => theme.textTypeBox}; +padding-right: 4px; +} +.correct-wordcard-char{ +color: ${({ theme }) => theme.text}; +padding-right: 4px; +} +.sentence-char{ +color: ${({ theme }) => theme.textTypeBox}; +} +.correct-sentence-char{ +color: ${({ theme }) => theme.text}; +} +.sentence-input-field{ +color: ${({ theme }) => theme.textTypeBox}; +font-size: 28px; +background: transparent; +border: none; +caret-color: ${({ theme }) => theme.stats}; +outline: none; +padding: 0; +font-family: ${({ theme }) => theme.fontFamily}; +} +.sentence-display-field{ +font-size: 28px; +} +.wordcard-word-display-field{ +font-size: 64px; +margin: 40px; +} +.wordcard-meaning-display-field{ +font-size: 20px; +margin-top: 40px; +margin-bottom: 10px; +} +.next-sentence-display{ +font-family: ${({ theme }) => theme.fontFamily}; +color: ${({ theme }) => theme.textTypeBox}; +display: block; +margin-top: 10px; +font-size: 16px; +} +.type-box-sentence { +display: block; +max-width: 1000px; +height: 240px; +overflow: hidden; +margin-left: auto; +margin-right: auto; +position: relative +top: 10%; +@media only screen +and (min-device-width: 375px) +and (max-device-width: 812px) +and (-webkit-min-device-pixel-ratio: 3) { +top:200px; +width: 60%; +} +} - .keyboard { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-bottom: 40px; - } +.keyboard { +display: flex; +flex-direction: column; +align-items: center; +justify-content: center; +margin-bottom: 40px; +} - .row { - list-style: none; - display: flex; - } - .row-1{ - padding-left: 0em; - } - .row-2{ - padding-left: 0.25em; - } - .row-3{ - padding-left: 0.5em; - } - .row-4{ - padding-left: 0em; - } +.row { +list-style: none; +display: flex; +} +.row-1{ +padding-left: 0em; +} +.row-2{ +padding-left: 0.25em; +} +.row-3{ +padding-left: 0.5em; +} +.row-4{ +padding-left: 0em; +} - ul { - display: block; - list-style-type: disc; - margin-block-start: 0.25em; - margin-block-end: 0.25em; - margin-inline-start: 0px; - margin-inline-end: 0px; - padding-inline-start: 0px; - } - .SPACEKEY { - height: 3em; - width: 21em; - color: ${({ theme }) => theme.text}; - font-family: ${({ theme }) => theme.fontFamily}; - border-radius: 0.4em; - line-height: 3em; - letter-spacing: 1px; - margin: 0.4em; - transition: 0.3s; - text-align: center; - font-size: 1em; - background-color: ${({ theme }) => theme.background}; - border: 2px solid ${({ theme }) => theme.textTypeBox}; - opacity: 0.8; - } - .UNITKEY { - height: 3em; - width: 3em; - color: rgba(0,0,0,0.7); - border-radius: 0.4em; - line-height: 3em; - letter-spacing: 1px; - margin: 0.4em; - transition: 0.3s; - text-align: center; - font-size: 1em; - font-family: ${({ theme }) => theme.fontFamily}; - background-color: ${({ theme }) => theme.background}; - border: 2px solid ${({ theme }) => theme.textTypeBox}; - opacity: 1; - color: ${({ theme }) => theme.text}; - opacity: 0.8; - } - .VIBRATE { - background-color: ${({ theme }) => theme.textTypeBox}; - -webkit-animation: vibrate-1 0.8s linear infinite both; - animation: vibrate-1 0.8s linear infinite both; - } - .VIBRATE-ERROR { - background-color: red; - -webkit-animation: vibrate-1 0.2s linear infinity both; - animation: vibrate-1 0.2s linear infinity both; - } - .NOVIBRATE-CORRECT { - background-color: ${({ theme }) => theme.textTypeBox}; - } +ul { +display: block; +list-style-type: disc; +margin-block-start: 0.25em; +margin-block-end: 0.25em; +margin-inline-start: 0px; +margin-inline-end: 0px; +padding-inline-start: 0px; +} +.SPACEKEY { +height: 3em; +width: 21em; +color: ${({ theme }) => theme.text}; +font-family: ${({ theme }) => theme.fontFamily}; +border-radius: 0.4em; +line-height: 3em; +letter-spacing: 1px; +margin: 0.4em; +transition: 0.3s; +text-align: center; +font-size: 1em; +background-color: ${({ theme }) => theme.background}; +border: 2px solid ${({ theme }) => theme.textTypeBox}; +opacity: 0.8; +} +.UNITKEY { +height: 3em; +width: 3em; +color: rgba(0,0,0,0.7); +border-radius: 0.4em; +line-height: 3em; +letter-spacing: 1px; +margin: 0.4em; +transition: 0.3s; +text-align: center; +font-size: 1em; +font-family: ${({ theme }) => theme.fontFamily}; +background-color: ${({ theme }) => theme.background}; +border: 2px solid ${({ theme }) => theme.textTypeBox}; +opacity: 1; +color: ${({ theme }) => theme.text}; +opacity: 0.8; +} +.VIBRATE { +background-color: ${({ theme }) => theme.textTypeBox}; +-webkit-animation: vibrate-1 0.8s linear infinite both; +animation: vibrate-1 0.8s linear infinite both; +} +.VIBRATE-ERROR { +background-color: red; +-webkit-animation: vibrate-1 0.2s linear infinity both; +animation: vibrate-1 0.2s linear infinity both; +} +.NOVIBRATE-CORRECT { +background-color: ${({ theme }) => theme.textTypeBox}; +} - @keyframes vibrate-1 { - 0% { - -webkit-transform: translate(0); - transform: translate(0); - } - 20% { - -webkit-transform: translate(-2px, 2px); - transform: translate(-2px, 2px); - } - 40% { - -webkit-transform: translate(-2px, -2px); - transform: translate(-2px, -2px); - } - 60% { - -webkit-transform: translate(2px, 2px); - transform: translate(2px, 2px); - } - 80% { - -webkit-transform: translate(2px, -2px); - transform: translate(2px, -2px); - } - 100% { - -webkit-transform: translate(0); - transform: translate(0); - } - } - .CorrectKeyDowns{ - color: inherit; - } - .IncorrectKeyDowns{ - color: red; - } - .words-card-container{ - display: block; - width: 100%; - height: 100%; - } - .words-card-catalog{ - width: 10%; - float: left; - text-align: left; - border-left: 2px groove ${({ theme }) => theme.stats}; - border-top: 1px solid ${({ theme }) => theme.stats}; - border-radius: 12px; - padding-left: 20px; - } - .words-card-main{ - width: 80%; - height: 90%; - float: left; - text-align: center; - } - .Catalog{ - list-style-type: none; - padding: 10px; - max-height: 300px; - margin-bottom: 5px; - overflow: hidden; - overflow-y:scroll; - text-align: left; - margin-top: 10px; - } - .Catalog::-webkit-scrollbar{ - width:5px; - } - .Catalog::-webkit-scrollbar-track{ - background:transparent; - } - .Catalog::-webkit-scrollbar-thumb{ - background:${({ theme }) => theme.stats}; - border-radius:12px; - } - .Catalog-title{ - margin-top: 20px; - margin-bottom: 10px; - } - .Catalog-li{ - cursor:pointer; - margin-bottom: 10px; - color: ${({ theme }) => theme.textTypeBox}; - } - .Catalog-li-Activated{ - cursor:default; - margin-bottom: 10px; - color: ${({ theme }) => theme.stats}; - } - .Catalog-Button{ - background-color: ${({ theme }) => theme.background}; - color: ${({ theme }) => theme.textTypeBox}; - } - .Catalog-Button-Activated{ - background-color: ${({ theme }) => theme.background}; - color: ${({ theme }) => theme.stats}; - } - .Catalog-Selected{ - background-color: ${({ theme }) => theme.background}; - color: ${({ theme }) => theme.textTypeBox}; - margin-top: 20px; - } - .select-chapter-title{ - font-size: 16px; - } - .fade-element { - opacity: 0; - transition: opacity 500ms ease-in-out; - } - .fade-element:hover { - opacity: 1; - transition: opacity 500ms ease-in-out; - } +@keyframes vibrate-1 { +0% { +-webkit-transform: translate(0); +transform: translate(0); +} +20% { +-webkit-transform: translate(-2px, 2px); +transform: translate(-2px, 2px); +} +40% { +-webkit-transform: translate(-2px, -2px); +transform: translate(-2px, -2px); +} +60% { +-webkit-transform: translate(2px, 2px); +transform: translate(2px, 2px); +} +80% { +-webkit-transform: translate(2px, -2px); +transform: translate(2px, -2px); +} +100% { +-webkit-transform: translate(0); +transform: translate(0); +} +} +.CorrectKeyDowns{ +color: inherit; +} +.IncorrectKeyDowns{ +color: red; +} +.words-card-container{ +display: block; +width: 100%; +height: 100%; +} +.words-card-catalog{ +width: 10%; +float: left; +text-align: left; +border-left: 2px groove ${({ theme }) => theme.stats}; +border-top: 1px solid ${({ theme }) => theme.stats}; +border-radius: 12px; +padding-left: 20px; +} +.words-card-main{ +width: 80%; +height: 90%; +float: left; +text-align: center; +} +.Catalog{ +list-style-type: none; +padding: 10px; +max-height: 300px; +margin-bottom: 5px; +overflow: hidden; +overflow-y:scroll; +text-align: left; +margin-top: 10px; +} +.Catalog::-webkit-scrollbar{ +width:5px; +} +.Catalog::-webkit-scrollbar-track{ +background:transparent; +} +.Catalog::-webkit-scrollbar-thumb{ +background:${({ theme }) => theme.stats}; +border-radius:12px; +} +.Catalog-title{ +margin-top: 20px; +margin-bottom: 10px; +} +.Catalog-li{ +cursor:pointer; +margin-bottom: 10px; +color: ${({ theme }) => theme.textTypeBox}; +} +.Catalog-li-Activated{ +cursor:default; +margin-bottom: 10px; +color: ${({ theme }) => theme.stats}; +} +.Catalog-Button{ +background-color: ${({ theme }) => theme.background}; +color: ${({ theme }) => theme.textTypeBox}; +} +.Catalog-Button-Activated{ +background-color: ${({ theme }) => theme.background}; +color: ${({ theme }) => theme.stats}; +} +.Catalog-Selected{ +background-color: ${({ theme }) => theme.background}; +color: ${({ theme }) => theme.textTypeBox}; +margin-top: 20px; +} +.select-chapter-title{ +font-size: 16px; +} +.fade-element { +opacity: 0; +transition: opacity 500ms ease-in-out; +} +.fade-element:hover { +opacity: 1; +transition: opacity 500ms ease-in-out; +} `; From 46b5d50b80f07ff870e51002de5bdc8cbf24197f Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Wed, 21 Aug 2024 21:48:33 +0700 Subject: [PATCH 19/28] fix/memory leak from wordGeneratorWorker --- src/components/features/TypeBox/Stats.js | 64 ++++---------- src/components/features/TypeBox/TypeBox.js | 87 ++++++++++--------- .../features/WordsCard/WordsCard.js | 1 + src/scripts/wordsGenerator.js | 2 + src/style/global.js | 47 ++++++++-- src/worker/WorkerBuilder.js | 5 -- src/worker/trackHistoryWorker.js | 2 +- 7 files changed, 106 insertions(+), 102 deletions(-) delete mode 100644 src/worker/WorkerBuilder.js diff --git a/src/components/features/TypeBox/Stats.js b/src/components/features/TypeBox/Stats.js index 923118f..34ba1c3 100644 --- a/src/components/features/TypeBox/Stats.js +++ b/src/components/features/TypeBox/Stats.js @@ -37,6 +37,7 @@ const Stats = ({ worker.onmessage = function (e) { setRoundedRawWpm(e.data); + worker.terminate(); }; return () => worker.terminate(); @@ -104,39 +105,6 @@ const Stats = ({ } }, [countDown]); - const primaryStatsTitleStyles = { - color: theme.textTypeBox, - marginBlock: 0, - marginBottom: "6px", - fontSize: "20px", - }; - - const primaryStatsValueStyles = { - marginBlock: 0, - fontSize: "36px", - color: theme.text, - }; - - const statsTitleStyles = { - color: theme.textTypeBox, - marginBlock: 0, - marginBottom: "6px", - fontWeight: "bold", - fontSize: "16px", - }; - - const statsValueStyles = { - marginBlock: 0, - }; - - const tooltipStyles = { - fontSize: "14px", - lineHeight: "6px", - display: "flex", - alignItems: "center", - gap: "8px", - }; - const getFormattedLanguageLanguageName = (value) => { switch (value) { case "ENGLISH_MODE": @@ -155,8 +123,8 @@ const Stats = ({ } >
-

Characters

-

+

Characters

+

{statsCharCount[1]}/ {statsCharCount[2]}/ {statsCharCount[3]}/ @@ -187,15 +155,15 @@ const Stats = ({

{`Time: ${label} s`}

-

+

{renderIndicator(red[400])} {`Errors: ${payloadData.error}`}

-

+

{renderIndicator(theme.textTypeBox)} {`Raw WPM: ${payloadData.rawWpm}`}

-

+

{renderIndicator(theme.text)} {`WPM: ${payloadData.wpm}`}

@@ -208,15 +176,15 @@ const Stats = ({ const renderAccuracy = () => (
-

ACC

-

{accuracy}%

+

ACC

+

{accuracy}%

); const renderRawKpm = () => (
-

KPM

-

+

KPM

+

{Math.round((rawKeyStrokes / Math.max(countDownConstant, 1)) * 60.0)}

@@ -224,8 +192,8 @@ const Stats = ({ const renderLanguage = () => (
-

Test Mode

-

+

Test Mode

+

{getFormattedLanguageLanguageName(language)}

@@ -233,8 +201,8 @@ const Stats = ({ const renderTime = () => (
-

Time

-

{countDownConstant} s

+

Time

+

{countDownConstant} s

); @@ -243,8 +211,8 @@ const Stats = ({ const averageWpm = data.length > 1 ? totalWpm / (data.length - 1) : 0; return (
-

WPM

-

{Math.round(averageWpm)}

+

WPM

+

{Math.round(averageWpm)}

); }; diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index e735464..60b9c3d 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -95,7 +95,7 @@ const TypeBox = ({ SYMBOL_ADDON_KEY ); - const [itemsToRender, setItemsToRender] = useState(40); + const [itemsToRender, setItemsToRender] = useState(50); // Caps Lock const [capsLocked, setCapsLocked] = useState(false); @@ -118,7 +118,7 @@ const TypeBox = ({ if (e.keyCode === 13 || e.keyCode === 9) { e.preventDefault(); setOpenRestart(false); - setItemsToRender(40); + setItemsToRender(50); reset( countDownConstant, difficulty, @@ -131,7 +131,7 @@ const TypeBox = ({ else if (e.keyCode === 32) { e.preventDefault(); setOpenRestart(false); - setItemsToRender(40); + setItemsToRender(50); reset( countDownConstant, difficulty, @@ -643,7 +643,6 @@ const TypeBox = ({ setPrevInput(updatedPrevInput); setWpmKeyStrokes(updatedWpmKeyStrokes); - // Always move to the next word setCurrWordIndex((prevIndex) => prevIndex + 1); setCurrInput(""); setCurrCharIndex(-1); @@ -1118,8 +1117,8 @@ const TypeBox = ({ useEffect(() => { const distanceToEnd = currentWords.length - 1 - currWordIndex; - if (distanceToEnd === 20) { - setItemsToRender((prev) => prev + 20); + if (distanceToEnd === 25) { + setItemsToRender((prev) => prev + 25); } }, [currWordIndex]); @@ -1231,6 +1230,44 @@ const TypeBox = ({ } }, [status, isShouldShowModal]); + const renderEnglishMode = () => ( +
+
+ {currentWords.map((word, i) => { + const opacityValue = Math.max( + 1 - Math.abs(i - currWordIndex) * 0.1, + 0.1 + ); + + return ( + + {word.split("").map((char, idx) => ( + + {char} + + ))} + {getExtraCharsDisplay(word, i)} + + ); + })} +
+
+ ); + return ( <>
- {language === ENGLISH_MODE && ( -
-
- {currentWords.map((word, i) => { - const opacityValue = - i >= currWordIndex && i < currWordIndex + 3 - ? 1 - : Math.max(1 - Math.abs(i - currWordIndex) * 0.1, 0.1); - - return ( - - {word.split("").map((char, idx) => ( - - {char} - - ))} - {getExtraCharsDisplay(word, i)} - - ); - })} -
-
- )} + {language === ENGLISH_MODE && renderEnglishMode()} {language === CHINESE_MODE && (
{ }; const handleKeyDown = (e) => { + console.log("handleKeyDown"); if (soundMode) { play(); } diff --git a/src/scripts/wordsGenerator.js b/src/scripts/wordsGenerator.js index f95f091..0f8b9ed 100644 --- a/src/scripts/wordsGenerator.js +++ b/src/scripts/wordsGenerator.js @@ -35,10 +35,12 @@ const wordsGenerator = ( worker.onmessage = function (e) { const generatedWords = e.data; resolve(generatedWords); + worker.terminate(); }; worker.onerror = function (e) { reject(e); + worker.terminate(); }; worker.postMessage({ diff --git a/src/style/global.js b/src/style/global.js index 00cbeb6..347b18c 100644 --- a/src/style/global.js +++ b/src/style/global.js @@ -1,11 +1,11 @@ import { createGlobalStyle } from "styled-components"; export const GlobalStyles = createGlobalStyle` - *, - *::after, - *::before { - box-sizing: border-box; - } +*, +*::after, +*::before { +box-sizing: border-box; +} body { display: flex; flex-direction: column; @@ -132,6 +132,7 @@ max-width: 1000px; left: 50%; transform: translate(-50%, -50%); display: flex; +padding-inline: 1rem; flex-direction: column; gap: 20px; } @@ -297,17 +298,21 @@ border-right: 1px solid transparent; border-left: 1px solid transparent; border-right: 1px solid transparent; color: ${({ theme }) => theme.text}; + } .error-char{ border-left: 1px solid transparent; border-right: 1px solid transparent; color: red; + } .caret-char-left{ border-left: 1px solid ${({ theme }) => theme.stats}; border-right: 1px solid transparent; + } .caret-char-left-start{ + border-left: 1px solid; border-right: 1px solid transparent; animation: blinkingCaretLeft 2s infinite; @@ -323,21 +328,25 @@ animation-timing-function: ease; .caret-char-right{ border-right: 1px solid ${({ theme }) => theme.stats}; border-left: 1x solid transparent; + } .caret-char-right-correct{ color: ${({ theme }) => theme.text}; border-right: 1px solid ${({ theme }) => theme.stats}; border-left: 1px solid transparent; + } .caret-char-right-error{ color: red; border-right: 1px solid ${({ theme }) => theme.stats}; border-left: 1px solid transparent; + } .caret-extra-char-right-error{ color: red; border-right: 1px solid ${({ theme }) => theme.stats}; border-left: 1px solid transparent; + } .hidden-input{ @@ -759,4 +768,32 @@ transition: opacity 500ms ease-in-out; opacity: 1; transition: opacity 500ms ease-in-out; } +.primary-stats-title { +color: ${({ theme }) => theme.textTypeBox}; +margin-block: 0; +margin-bottom: 6px; +font-size: 20px; +} +.primary-stats-value { +color: ${({ theme }) => theme.text}; +margin-block: 0; +font-size: 36px; +} +.stats-title { +color: ${({ theme }) => theme.textTypeBox}; +margin-block: 0; +margin-bottom: 6px; +font-weight: bold; +font-size: 16px; +} +.stats-value { +margin-block: 0; +} +.tooltip { +font-size: 14px; +line-height: 6px; +display: flex; +align-items: center; +gap: 8px; +} `; diff --git a/src/worker/WorkerBuilder.js b/src/worker/WorkerBuilder.js deleted file mode 100644 index 8737fc9..0000000 --- a/src/worker/WorkerBuilder.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function WorkerBuilder(worker) { - const code = worker.toString(); - const blob = new Blob(["(" + code + ")()"]); - return new Worker(URL.createObjectURL(blob)); -} diff --git a/src/worker/trackHistoryWorker.js b/src/worker/trackHistoryWorker.js index 3bf47fb..c39b890 100644 --- a/src/worker/trackHistoryWorker.js +++ b/src/worker/trackHistoryWorker.js @@ -15,10 +15,10 @@ self.onmessage = function (e) { switch (countDownConstant) { case 90: case 60: + case 30: shouldRecord = countDown % 5 === 0; increment = 5; break; - case 30: case 15: shouldRecord = true; increment = 1; From be4af4e415e343c04c7372298116bcc766d84f34 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Thu, 22 Aug 2024 07:50:46 +0700 Subject: [PATCH 20/28] remove unnecessary web workers to prevent performance problems --- src/components/features/TypeBox/TypeBox.js | 143 +++++++----------- .../features/WordsCard/WordsCard.js | 1 - src/scripts/wordsGenerator.js | 60 ++++---- src/worker/checkPrevWorker.js | 64 -------- src/worker/getCharClassNameWorker.js | 76 ---------- src/worker/getWordClassNameWorker.js | 37 ----- src/worker/wordsGeneratorWorker.js | 77 ---------- 7 files changed, 89 insertions(+), 369 deletions(-) delete mode 100644 src/worker/checkPrevWorker.js delete mode 100644 src/worker/getCharClassNameWorker.js delete mode 100644 src/worker/getWordClassNameWorker.js delete mode 100644 src/worker/wordsGeneratorWorker.js diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 60b9c3d..73df357 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -95,7 +95,7 @@ const TypeBox = ({ SYMBOL_ADDON_KEY ); - const [itemsToRender, setItemsToRender] = useState(50); + const [itemsToRender, setItemsToRender] = useState(60); // Caps Lock const [capsLocked, setCapsLocked] = useState(false); @@ -118,7 +118,7 @@ const TypeBox = ({ if (e.keyCode === 13 || e.keyCode === 9) { e.preventDefault(); setOpenRestart(false); - setItemsToRender(50); + setItemsToRender(60); reset( countDownConstant, difficulty, @@ -131,7 +131,7 @@ const TypeBox = ({ else if (e.keyCode === 32) { e.preventDefault(); setOpenRestart(false); - setItemsToRender(50); + setItemsToRender(60); reset( countDownConstant, difficulty, @@ -150,34 +150,25 @@ const TypeBox = ({ }; // set up words state - const [wordsDict, setWordsDict] = useState([]); - - useEffect(() => { + const [wordsDict, setWordsDict] = useState(() => { if (language === ENGLISH_MODE) { - (async () => { - const generatedWords = await wordsGenerator( - DEFAULT_WORDS_COUNT, - difficulty, - ENGLISH_MODE, - numberAddOn, - symbolAddOn - ); - - setWordsDict(generatedWords); - })(); + return wordsGenerator( + DEFAULT_WORDS_COUNT, + difficulty, + ENGLISH_MODE, + numberAddOn, + symbolAddOn + ); } - if (language === CHINESE_MODE) { - const generatedWords = chineseWordsGenerator( + return chineseWordsGenerator( difficulty, CHINESE_MODE, numberAddOn, symbolAddOn ); - - setWordsDict(generatedWords); } - }, []); + }); const words = useMemo(() => { return wordsDict.map((e) => e.val); @@ -232,16 +223,14 @@ const TypeBox = ({ useEffect(() => { if (currWordIndex === DEFAULT_WORDS_COUNT - 1) { if (language === ENGLISH_MODE) { - (async () => { - const generatedEng = await wordsGenerator( - DEFAULT_WORDS_COUNT, - difficulty, - ENGLISH_MODE, - numberAddOn, - symbolAddOn - ); - setWordsDict((currentArray) => [...currentArray, ...generatedEng]); - })(); + const generatedEng = wordsGenerator( + DEFAULT_WORDS_COUNT, + difficulty, + ENGLISH_MODE, + numberAddOn, + symbolAddOn + ); + setWordsDict((currentArray) => [...currentArray, ...generatedEng]); } if (language === CHINESE_MODE) { const generatedChinese = chineseWordsGenerator( @@ -292,17 +281,15 @@ const TypeBox = ({ ); } if (language === ENGLISH_MODE) { - (async () => { - const generatedWords = await wordsGenerator( + setWordsDict( + wordsGenerator( DEFAULT_WORDS_COUNT, difficulty, language, newNumberAddOn, newSymbolAddOn - ); - - setWordsDict(generatedWords); - })(); + ) + ); } } setNumberAddOn(newNumberAddOn); @@ -597,57 +584,37 @@ const TypeBox = ({ } }; - const workerRef = useRef(null); - - useEffect(() => { - workerRef.current = new Worker( - new URL("../../../worker/checkPrevWorker", import.meta.url) - ); - - return () => { - if (workerRef.current) { - workerRef.current.terminate(); - } - }; - }, []); - const checkPrev = () => { - if (!workerRef.current) return; - + const wordToCompare = words[currWordIndex]; const currInputWithoutSpaces = currInput.trim(); - workerRef.current.postMessage({ - words, - currWordIndex, - currInputWithoutSpaces, - wordsCorrect: Array.from(wordsCorrect), - wordsInCorrect: Array.from(wordsInCorrect), - inputWordsHistory, - prevInput, - wpmKeyStrokes, - }); + const isCorrect = wordToCompare === currInputWithoutSpaces; + if (!currInputWithoutSpaces || currInputWithoutSpaces.length === 0) { + return null; + } + if (isCorrect) { + // console.log("detected match"); + wordsCorrect.add(currWordIndex); + wordsInCorrect.delete(currWordIndex); + let inputWordsHistoryUpdate = { ...inputWordsHistory }; + inputWordsHistoryUpdate[currWordIndex] = currInputWithoutSpaces; + setInputWordsHistory(inputWordsHistoryUpdate); + // reset prevInput to empty (will not go back) + setPrevInput(""); - workerRef.current.onmessage = (event) => { - const { - isCorrect, - updatedWordsCorrect, - updatedWordsInCorrect, - updatedInputWordsHistory, - updatedPrevInput, - updatedWpmKeyStrokes, - } = event.data; - - if (isCorrect !== null) { - setWordsCorrect(new Set(updatedWordsCorrect)); - setWordsInCorrect(new Set(updatedWordsInCorrect)); - setInputWordsHistory(updatedInputWordsHistory); - setPrevInput(updatedPrevInput); - setWpmKeyStrokes(updatedWpmKeyStrokes); - - setCurrWordIndex((prevIndex) => prevIndex + 1); - setCurrInput(""); - setCurrCharIndex(-1); - } - }; + // here count the space as effective wpm. + setWpmKeyStrokes(wpmKeyStrokes + 1); + return true; + } else { + // console.log("detected unmatch"); + wordsInCorrect.add(currWordIndex); + wordsCorrect.delete(currWordIndex); + let inputWordsHistoryUpdate = { ...inputWordsHistory }; + inputWordsHistoryUpdate[currWordIndex] = currInputWithoutSpaces; + setInputWordsHistory(inputWordsHistoryUpdate); + // append currInput to prevInput + setPrevInput(prevInput + " " + currInputWithoutSpaces); + return false; + } }; const getWordClassName = (wordIdx) => { @@ -1117,8 +1084,8 @@ const TypeBox = ({ useEffect(() => { const distanceToEnd = currentWords.length - 1 - currWordIndex; - if (distanceToEnd === 25) { - setItemsToRender((prev) => prev + 25); + if (distanceToEnd === 30) { + setItemsToRender((prev) => prev + 30); } }, [currWordIndex]); diff --git a/src/components/features/WordsCard/WordsCard.js b/src/components/features/WordsCard/WordsCard.js index 1c976ca..f9a4714 100644 --- a/src/components/features/WordsCard/WordsCard.js +++ b/src/components/features/WordsCard/WordsCard.js @@ -267,7 +267,6 @@ const WordsCard = ({ soundType, soundMode }) => { }; const handleKeyDown = (e) => { - console.log("handleKeyDown"); if (soundMode) { play(); } diff --git a/src/scripts/wordsGenerator.js b/src/scripts/wordsGenerator.js index 0f8b9ed..f9d8ff2 100644 --- a/src/scripts/wordsGenerator.js +++ b/src/scripts/wordsGenerator.js @@ -27,34 +27,42 @@ const wordsGenerator = ( numberAddOn, symbolAddOn ) => { - return new Promise((resolve, reject) => { - const worker = new Worker( - new URL("../worker/wordsGeneratorWorker.js", import.meta.url) - ); - - worker.onmessage = function (e) { - const generatedWords = e.data; - resolve(generatedWords); - worker.terminate(); - }; - - worker.onerror = function (e) { - reject(e); - worker.terminate(); - }; + if (languageMode === ENGLISH_MODE) { + if (difficulty === DEFAULT_DIFFICULTY) { + const EnglishWordList = []; + for (let i = 0; i < DEFAULT_WORDS_COUNT; i++) { + const rand = randomIntFromRange(0, 550); + let wordCandidate = COMMON_WORDS[rand].val; + if (numberAddOn) { + wordCandidate = wordCandidate + generateRandomNumChras(1, 2); + } + if (symbolAddOn) { + wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); + } + EnglishWordList.push({ key: wordCandidate, val: wordCandidate }); + } + return EnglishWordList; + } - worker.postMessage({ - wordsCount, - ENGLISH_MODE, - COMMON_WORDS, - DEFAULT_DIFFICULTY, - DEFAULT_WORDS_COUNT, - difficulty, - languageMode, - numberAddOn, - symbolAddOn, + // hard + const randomWordsGenerated = randomWords({ + exactly: wordsCount, + maxLength: 7, }); - }); + const words = []; + for (let i = 0; i < wordsCount; i++) { + let wordCandidate = randomWordsGenerated[i]; + if (numberAddOn) { + wordCandidate = wordCandidate + generateRandomNumChras(1, 2); + } + if (symbolAddOn) { + wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); + } + words.push({ key: wordCandidate, val: wordCandidate }); + } + return words; + } + return ["something", "went", "wrong"]; }; const chineseWordsGenerator = ( diff --git a/src/worker/checkPrevWorker.js b/src/worker/checkPrevWorker.js deleted file mode 100644 index 723ceed..0000000 --- a/src/worker/checkPrevWorker.js +++ /dev/null @@ -1,64 +0,0 @@ -// eslint-disable-next-line no-restricted-globals -self.onmessage = function (event) { - const { - words, - currWordIndex, - currInputWithoutSpaces, - wordsCorrect, - wordsInCorrect, - inputWordsHistory, - prevInput, - wpmKeyStrokes, - } = event.data; - - // Validate input data - const wordToCompare = words[currWordIndex]; - const isCorrect = wordToCompare === currInputWithoutSpaces; - - // Ensure wpmKeyStrokes is a valid number - const validWpmKeyStrokes = - typeof wpmKeyStrokes === "number" && !isNaN(wpmKeyStrokes) - ? wpmKeyStrokes - : 0; - - // Check if the current input is empty - if (!currInputWithoutSpaces || currInputWithoutSpaces.length === 0) { - postMessage({ isCorrect: null }); - return; - } - - let updatedWordsCorrect = new Set(wordsCorrect); - let updatedWordsInCorrect = new Set(wordsInCorrect); - let updatedInputWordsHistory = { ...inputWordsHistory }; - let updatedPrevInput = prevInput; - let updatedWpmKeyStrokes = validWpmKeyStrokes; - - if (isCorrect) { - updatedWordsCorrect.add(currWordIndex); - updatedWordsInCorrect.delete(currWordIndex); - updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; - updatedPrevInput = ""; - updatedWpmKeyStrokes += 1; // Increment only if valid - postMessage({ - isCorrect: true, - updatedWordsCorrect: Array.from(updatedWordsCorrect), - updatedWordsInCorrect: Array.from(updatedWordsInCorrect), - updatedInputWordsHistory, - updatedPrevInput, - updatedWpmKeyStrokes, - }); - } else { - updatedWordsInCorrect.add(currWordIndex); - updatedWordsCorrect.delete(currWordIndex); - updatedInputWordsHistory[currWordIndex] = currInputWithoutSpaces; - updatedPrevInput += " " + currInputWithoutSpaces; - postMessage({ - isCorrect: false, - updatedWordsCorrect: Array.from(updatedWordsCorrect), - updatedWordsInCorrect: Array.from(updatedWordsInCorrect), - updatedInputWordsHistory, - updatedPrevInput, - updatedWpmKeyStrokes, - }); - } -}; diff --git a/src/worker/getCharClassNameWorker.js b/src/worker/getCharClassNameWorker.js deleted file mode 100644 index cd861bf..0000000 --- a/src/worker/getCharClassNameWorker.js +++ /dev/null @@ -1,76 +0,0 @@ -// eslint-disable-next-line import/no-anonymous-default-export -export default () => { - // eslint-disable-next-line no-restricted-globals - self.onmessage = function (e) { - const { - wordIdx, - charIdx, - pacingStyle, - PACING_CARET, - currWordIndex, - currCharIndex, - status, - history, - char, - currChar, - word, - } = e.data; - - const keyString = wordIdx + "." + charIdx; - let className; - - if ( - pacingStyle === PACING_CARET && - wordIdx === currWordIndex && - charIdx === currCharIndex + 1 && - status !== "finished" - ) { - className = "caret-char-left"; - } else if (history[keyString] === true) { - if ( - pacingStyle === PACING_CARET && - wordIdx === currWordIndex && - word.length - 1 === currCharIndex && - charIdx === currCharIndex && - status !== "finished" - ) { - className = "caret-char-right-correct"; - } else { - className = "correct-char"; - } - } else if (history[keyString] === false) { - if ( - pacingStyle === PACING_CARET && - wordIdx === currWordIndex && - word.length - 1 === currCharIndex && - charIdx === currCharIndex && - status !== "finished" - ) { - className = "caret-char-right-error"; - } else { - className = "error-char"; - } - } else if ( - wordIdx === currWordIndex && - charIdx === currCharIndex && - currChar && - status !== "finished" - ) { - if (char === currChar) { - history[keyString] = true; - className = "correct-char"; - } else { - history[keyString] = false; - className = "error-char"; - } - } else { - if (wordIdx < currWordIndex) { - // missing chars - history[keyString] = undefined; - } - className = "char"; - } - - postMessage(className); - }; -}; diff --git a/src/worker/getWordClassNameWorker.js b/src/worker/getWordClassNameWorker.js deleted file mode 100644 index bf725e4..0000000 --- a/src/worker/getWordClassNameWorker.js +++ /dev/null @@ -1,37 +0,0 @@ -// eslint-disable-next-line import/no-anonymous-default-export -export default () => { - // eslint-disable-next-line no-restricted-globals - self.onmessage = function (e) { - const { - wordsInCorrect, - wordIdx, - currWordIndex, - pacingStyle, - PACING_PULSE, - } = e.data; - - let className = "word"; - - if (wordsInCorrect.has(wordIdx)) { - if (currWordIndex === wordIdx) { - if (pacingStyle === PACING_PULSE) { - className = "word error-word active-word"; - } else { - className = "word error-word active-word-no-pulse"; - } - } else { - className = "word error-word"; - } - } else { - if (currWordIndex === wordIdx) { - if (pacingStyle === PACING_PULSE) { - className = "word active-word"; - } else { - className = "word active-word-no-pulse"; - } - } - } - - postMessage(className); - }; -}; diff --git a/src/worker/wordsGeneratorWorker.js b/src/worker/wordsGeneratorWorker.js deleted file mode 100644 index eabd47f..0000000 --- a/src/worker/wordsGeneratorWorker.js +++ /dev/null @@ -1,77 +0,0 @@ -import randomWords from "random-words"; -// eslint-disable-next-line no-restricted-globals -self.onmessage = function (e) { - const { - ENGLISH_MODE, - COMMON_WORDS, - DEFAULT_DIFFICULTY, - DEFAULT_WORDS_COUNT, - wordsCount, - difficulty, - languageMode, - numberAddOn, - symbolAddOn, - } = e.data; - - const randomIntFromRange = (min, max) => - Math.floor(Math.random() * (max - min + 1)) + min; - const generateRandomNumChras = (min, max) => - String.fromCharCode(randomIntFromRange(min, max) + 48); - const generateRandomSymbolChras = (min, max) => - String.fromCharCode(randomIntFromRange(min, max) + 33); - - const wordsGenerator = ( - wordsCount, - difficulty, - languageMode, - numberAddOn, - symbolAddOn - ) => { - if (languageMode === ENGLISH_MODE) { - if (difficulty === DEFAULT_DIFFICULTY) { - const EnglishWordList = []; - for (let i = 0; i < DEFAULT_WORDS_COUNT; i++) { - const rand = randomIntFromRange(0, 550); - let wordCandidate = COMMON_WORDS[rand].val; - if (numberAddOn) { - wordCandidate = wordCandidate + generateRandomNumChras(1, 2); - } - if (symbolAddOn) { - wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); - } - EnglishWordList.push({ key: wordCandidate, val: wordCandidate }); - } - return EnglishWordList; - } - - // Hard difficulty - const randomWordsGenerated = randomWords({ - exactly: wordsCount, - maxLength: 7, - }); - const words = []; - for (let i = 0; i < wordsCount; i++) { - let wordCandidate = randomWordsGenerated[i]; - if (numberAddOn) { - wordCandidate = wordCandidate + generateRandomNumChras(1, 2); - } - if (symbolAddOn) { - wordCandidate = wordCandidate + generateRandomSymbolChras(1, 1); - } - words.push({ key: wordCandidate, val: wordCandidate }); - } - return words; - } - return ["something", "went", "wrong"]; - }; - - const result = wordsGenerator( - wordsCount, - difficulty, - languageMode, - numberAddOn, - symbolAddOn - ); - - postMessage(result); -}; From 3e57aa2c38d753172b99eaa2f48998847dc2eb97 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Thu, 22 Aug 2024 16:32:57 +0700 Subject: [PATCH 21/28] optimize english mode by memoization --- .firebase/hosting.YnVpbGQ.cache | 62 ++++---- src/components/common/EnglishModeWords.js | 61 +++++++ src/components/common/SocialLinksModal.js | 124 ++++++++++++++- src/components/features/TypeBox/TypeBox.js | 176 ++------------------- 4 files changed, 223 insertions(+), 200 deletions(-) create mode 100644 src/components/common/EnglishModeWords.js diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index a7e9aa2..025f15d 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,33 +1,29 @@ -manifest.json,1723915964268,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c -robots.txt,1723915964271,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec -logo512.png,1723915964268,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f -logo.png,1723915964268,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d -logo192.png,1723915964268,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 -favicon.ico,1723915964268,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 -asset-manifest.json,1723916035851,0fcd3da0228c1b968996fa6193237f62593b0069ff13fbc6abb2ce8310408516 -index.html,1723916035808,942bf9d2ff8c0d923a26c34448e140e1d7e81c768ec0a8b8551cc255e3b80029 -static/media/typeSoft.3e307070c0d43972460c.wav,1723916035811,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 -static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1723916035811,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae -static/js/main.70e758bd.js.LICENSE.txt,1723916035815,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 -static/js/911.b91b008e.chunk.js.map,1723916035855,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 -static/js/911.b91b008e.chunk.js,1723916035815,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f -static/js/845.d88f7a05.chunk.js,1723916035815,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e -static/js/514.1d957c45.chunk.js.map,1723916035858,4594e5484d45f44f923fc1d3ebc50bd5a0e8b442cc5ed50d6b1628a665818a1f -static/js/514.1d957c45.chunk.js,1723916035851,04e02477c807ca1b794aa240ee99956d5dc3e1f49a555a92a2f6211c6d7026fc -static/js/268.39edc2bf.chunk.js.map,1723916035858,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 -static/js/268.39edc2bf.chunk.js,1723916035818,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba -static/js/845.d88f7a05.chunk.js.LICENSE.txt,1723916035815,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e -static/js/215.64dd16d3.chunk.js,1723916035815,dbe653aa13fdd32a0819cf0f16b70627a7d5bf3ebafc500158a77c9a3fb0154b -static/js/181.50cd7f54.chunk.js.map,1723916035855,550e32b3c91c7d8e9e86941d23a485d5a816695d861d263d76cdaba764f52d3c -static/js/181.50cd7f54.chunk.js,1723916035815,4be0ba23b2efd5b77e9bc30ef1031482907754666cd70a2cb9f9e1a21ba93dd6 -static/js/110.abadb285.chunk.js.map,1723916035855,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 -static/js/110.abadb285.chunk.js,1723916035815,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 -static/css/main.e6c13ad2.css.map,1723916035851,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 -static/css/main.e6c13ad2.css,1723916035815,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 -static/js/215.64dd16d3.chunk.js.map,1723916035858,3adff4a7b76271572d1cc6fa0d8c6e3e34bd5c603eae979501978a88ab107bb1 -static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1723916035811,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 -static/media/keyboard.f9440324ce3fbfc79f5b.wav,1723916035815,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 -static/media/WeChatSupport.d4d0b5b8d76008986829.png,1723916035811,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d -static/js/845.d88f7a05.chunk.js.map,1723916035858,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 -static/js/main.70e758bd.js,1723916035815,4103832b0fac8fde09ffd4a9db03937ac0e3a523b066bcb8cb17894a0a350735 -static/js/main.70e758bd.js.map,1723916035855,00ed1893464f348a6e6c04f455b58d11ba4ed08caa5d0ee9e581b544a9a8f167 +robots.txt,1724301879504,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +logo192.png,1724301879503,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +index.html,1724301986496,f2049e4a03cf6d8d942370013d143e91b50d0d10155f5e3f7592442d26fd8b36 +manifest.json,1724301879503,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +logo.png,1724301879502,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +favicon.ico,1724301879502,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +asset-manifest.json,1724301986529,e42c23fd64865e4d8eadf36f8371816bf50a25904bacbd4017fd74d182d2bd9c +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1724301986499,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +logo512.png,1724301879503,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +static/js/main.04bda0e9.js.LICENSE.txt,1724301986499,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/media/typeSoft.3e307070c0d43972460c.wav,1724301986500,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +static/js/911.b91b008e.chunk.js,1724301986500,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1724301986499,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/845.d88f7a05.chunk.js,1724301986500,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/js/514.489e435b.chunk.js.map,1724301986531,68d375a86e92d47d24bfa1924a8b74f1720d1f15eb0752b69779022b8b7a9e28 +static/js/268.39edc2bf.chunk.js.map,1724301986531,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 +static/js/268.39edc2bf.chunk.js,1724301986500,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba +static/js/110.abadb285.chunk.js.map,1724301986531,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 +static/js/110.abadb285.chunk.js,1724301986500,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 +static/css/main.e6c13ad2.css.map,1724301986500,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/js/514.489e435b.chunk.js,1724301986500,45f4d679735604e53c02fcc5452e77d34d85a662b62088b932d66d4d9435f7ff +static/js/911.b91b008e.chunk.js.map,1724301986531,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 +static/css/main.e6c13ad2.css,1724301986499,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1724301986500,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1724301986500,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1724301986499,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/845.d88f7a05.chunk.js.map,1724301986531,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/js/main.04bda0e9.js,1724301986499,ae6442a8c493260f73bf094844d1970cef9f5849d69668230e0c550ab8f19084 +static/js/main.04bda0e9.js.map,1724301986530,b2cf332901f7e23257a78a30c6ca5f640d3a941951f6215c82345ed879b237f7 diff --git a/src/components/common/EnglishModeWords.js b/src/components/common/EnglishModeWords.js new file mode 100644 index 0000000..9d781f4 --- /dev/null +++ b/src/components/common/EnglishModeWords.js @@ -0,0 +1,61 @@ +import React, { memo } from "react"; + +// Define the component +const EnglishModeWords = ({ + currentWords, + currWordIndex, + isFocusedMode, + status, + wordSpanRefs, + getWordClassName, + getCharClassName, + getExtraCharsDisplay, +}) => { + const wordMap = {}; + + return ( +
+
+ {currentWords.map((word, i) => { + // Use a HashMap (object) to store the word by its index + if (!wordMap[i]) { + wordMap[i] = word; + } + + // Process the word immediately using the HashMap + const opacityValue = Math.max( + 1 - Math.abs(i - currWordIndex) * 0.1, + 0.1 + ); + + return ( + + {wordMap[i].split("").map((char, idx) => ( + + {char} + + ))} + {getExtraCharsDisplay(wordMap[i], i)} + + ); + })} +
+
+ ); +}; + +export default memo(EnglishModeWords); diff --git a/src/components/common/SocialLinksModal.js b/src/components/common/SocialLinksModal.js index 57531d6..0186a79 100644 --- a/src/components/common/SocialLinksModal.js +++ b/src/components/common/SocialLinksModal.js @@ -1,19 +1,133 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { IconButton, Typography, Box } from "@mui/material"; import { Facebook, Twitter, Instagram, LinkedIn } from "@mui/icons-material"; -const SocialLinksModal = ({ open, onClose, projectUrl }) => { - if (!open) return null; // Prevent rendering if the modal is not open +const SocialLinksModal = ({ status }) => { + const MODAL_DISPLAY_KEY = "modalDisplayedTimestamp"; // Key for local storage + const COUNTDOWN_KEY = "countdownStartTime"; // Key for countdown start time + const COUNTDOWN_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds + // State to manage the modal's open/close state + const [isShouldShowModal, setIsShouldShowModal] = useState(false); + const [remainingTime, setRemainingTime] = useState(COUNTDOWN_DURATION); + + // URL of your typing test project + const projectUrl = "https://www.eletypes.com"; const shareText = "Check out this typing test project!"; + const checkIfModalShouldBeDisplayed = () => { + const lastDisplayed = localStorage.getItem(MODAL_DISPLAY_KEY); + const countdownStartTime = parseInt( + localStorage.getItem(COUNTDOWN_KEY), + 10 + ); + const now = new Date().getTime(); + + if ( + !lastDisplayed || + now - parseInt(lastDisplayed, 10) >= COUNTDOWN_DURATION + ) { + // Show modal and record timestamp + setIsShouldShowModal(true); + localStorage.setItem(MODAL_DISPLAY_KEY, now.toString()); + } + }; + + const startCountdown = () => { + const now = new Date().getTime(); + localStorage.setItem(COUNTDOWN_KEY, now.toString()); + checkIfModalShouldBeDisplayed(); + }; + + const calculateRemainingTime = () => { + const countdownStartTime = parseInt( + localStorage.getItem(COUNTDOWN_KEY), + 10 + ); + const now = new Date().getTime(); + const elapsedTime = now - countdownStartTime; + const timeLeft = COUNTDOWN_DURATION - elapsedTime; + + if (timeLeft <= 0) { + setRemainingTime(0); + localStorage.removeItem(COUNTDOWN_KEY); // Clear the countdown start time + setIsShouldShowModal(true); + localStorage.setItem(MODAL_DISPLAY_KEY, now.toString()); // Update modal display timestamp + } else { + setRemainingTime(timeLeft); + } + }; + + const formatTime = (time) => { + const minutes = Math.floor(time / 60000); + const seconds = Math.floor((time % 60000) / 1000); + return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`; + }; + + // Initial setup and cleanup for countdown timer + useEffect(() => { + calculateRemainingTime(); // Initial calculation + const timer = setInterval(calculateRemainingTime, 1000); // Update every second + + // Start countdown if not already started + if (!localStorage.getItem(COUNTDOWN_KEY)) { + startCountdown(); + } + + return () => clearInterval(timer); // Cleanup on component unmount + }, []); + + // Manage cursor visibility and modal state based on status + useEffect(() => { + const body = document.getElementsByTagName("body")[0]; + const delay = 500; + let timeoutId; + + const showCursor = () => { + body.style.cursor = "default"; + clearTimeout(timeoutId); + timeoutId = setTimeout(hideCursor, delay); // Adjust timeout duration as needed + }; + + const hideCursor = () => { + body.style.cursor = "none"; + }; + + if (status === "started") { + body.style.cursor = "none"; // Initially hide the cursor + timeoutId = setTimeout(hideCursor, delay); // Set timeout to hide cursor after inactivity + + document.addEventListener("mousemove", showCursor); + + return () => { + document.removeEventListener("mousemove", showCursor); + clearTimeout(timeoutId); // Clear timeout on cleanup + body.style.cursor = "default"; // Reset cursor style on cleanup + }; + } else { + // Reset countdown and show modal if finished and should show modal + if (status === "finished" && isShouldShowModal) { + setIsShouldShowModal(false); // Hide the modal + localStorage.removeItem(COUNTDOWN_KEY); // Reset countdown start time + localStorage.removeItem(MODAL_DISPLAY_KEY); // Clear modal display timestamp + setRemainingTime(COUNTDOWN_DURATION); // Reset remaining time + } + // Ensure cursor is reset if status is not "started" + body.style.cursor = "default"; + } + }, [status, isShouldShowModal]); + // Handle clicks on the overlay const handleOverlayClick = (e) => { if (e.target === e.currentTarget) { - onClose(); + setIsShouldShowModal(false); } }; + if (!isShouldShowModal) { + return null; // Do not render anything if the modal should not be shown + } + return (
e.stopPropagation()}> @@ -75,7 +189,7 @@ const SocialLinksModal = ({ open, onClose, projectUrl }) => { setIsShouldShowModal(false)} style={{ position: "absolute", top: 10, right: 10 }} > diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 73df357..c373f0e 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -47,6 +47,7 @@ import { } from "../../../constants/Constants"; import { SOUND_MAP } from "../sound/sound"; import SocialLinksModal from "../../common/SocialLinksModal"; +import EnglishModeWords from "../../common/EnglishModeWords"; const TypeBox = ({ textInputRef, @@ -103,16 +104,6 @@ const TypeBox = ({ // tab-enter restart dialog const [openRestart, setOpenRestart] = useState(false); - // State to manage the modal's open/close state - const [modalOpen, setModalOpen] = useState(false); - - // URL of your typing test project - const projectUrl = "https://www.eletypes.com"; - - // Functions to open and close the modal - const handleOpenModal = () => setModalOpen(true); - const handleCloseModal = () => setModalOpen(false); - const EnterkeyPressReset = (e) => { // press enter/or tab to reset; if (e.keyCode === 13 || e.keyCode === 9) { @@ -1089,162 +1080,23 @@ const TypeBox = ({ } }, [currWordIndex]); - const MODAL_DISPLAY_KEY = "modalDisplayedTimestamp"; // Key for local storage - const COUNTDOWN_KEY = "countdownStartTime"; // Key for countdown start time - const COUNTDOWN_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds - - const [isShouldShowModal, setIsShouldShowModal] = useState(false); - const [remainingTime, setRemainingTime] = useState(COUNTDOWN_DURATION); - - const checkIfModalShouldBeDisplayed = () => { - const lastDisplayed = localStorage.getItem(MODAL_DISPLAY_KEY); - const countdownStartTime = parseInt( - localStorage.getItem(COUNTDOWN_KEY), - 10 - ); - const now = new Date().getTime(); - - if ( - !lastDisplayed || - now - parseInt(lastDisplayed, 10) >= COUNTDOWN_DURATION - ) { - // Show modal and record timestamp - setIsShouldShowModal(true); - localStorage.setItem(MODAL_DISPLAY_KEY, now.toString()); - } - }; - - const startCountdown = () => { - const now = new Date().getTime(); - localStorage.setItem(COUNTDOWN_KEY, now.toString()); - checkIfModalShouldBeDisplayed(); - }; - - const calculateRemainingTime = () => { - const countdownStartTime = parseInt( - localStorage.getItem(COUNTDOWN_KEY), - 10 - ); - const now = new Date().getTime(); - const elapsedTime = now - countdownStartTime; - const timeLeft = COUNTDOWN_DURATION - elapsedTime; - - if (timeLeft <= 0) { - setRemainingTime(0); - localStorage.removeItem(COUNTDOWN_KEY); // Clear the countdown start time - setIsShouldShowModal(true); - localStorage.setItem(MODAL_DISPLAY_KEY, now.toString()); // Update modal display timestamp - } else { - setRemainingTime(timeLeft); - } - }; - - const formatTime = (time) => { - const minutes = Math.floor(time / 60000); - const seconds = Math.floor((time % 60000) / 1000); - return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`; - }; - - useEffect(() => { - calculateRemainingTime(); // Initial calculation - const timer = setInterval(calculateRemainingTime, 1000); // Update every second - - // Start countdown if not already started - if (!localStorage.getItem(COUNTDOWN_KEY)) { - startCountdown(); - } - - return () => clearInterval(timer); // Cleanup on component unmount - }, []); - - useEffect(() => { - const body = document.getElementsByTagName("body")[0]; - const delay = 500; - let timeoutId; - - const showCursor = () => { - body.style.cursor = "default"; - clearTimeout(timeoutId); - timeoutId = setTimeout(hideCursor, delay); // Adjust timeout duration as needed - }; - - const hideCursor = () => { - body.style.cursor = "none"; - }; - - if (status === "started") { - body.style.cursor = "none"; // Initially hide the cursor - timeoutId = setTimeout(hideCursor, delay); // Set timeout to hide cursor after inactivity - - document.addEventListener("mousemove", showCursor); - - return () => { - document.removeEventListener("mousemove", showCursor); - clearTimeout(timeoutId); // Clear timeout on cleanup - body.style.cursor = "default"; // Reset cursor style on cleanup - }; - } else { - // Reset countdown and show modal if finished and should show modal - if (status === "finished" && isShouldShowModal) { - setIsShouldShowModal(false); // Hide the modal - localStorage.removeItem(COUNTDOWN_KEY); // Reset countdown start time - localStorage.removeItem(MODAL_DISPLAY_KEY); // Clear modal display timestamp - setRemainingTime(COUNTDOWN_DURATION); // Reset remaining time - handleOpenModal(); - } - // Ensure cursor is reset if status is not "started" - body.style.cursor = "default"; - } - }, [status, isShouldShowModal]); - - const renderEnglishMode = () => ( -
-
- {currentWords.map((word, i) => { - const opacityValue = Math.max( - 1 - Math.abs(i - currWordIndex) * 0.1, - 0.1 - ); - - return ( - - {word.split("").map((char, idx) => ( - - {char} - - ))} - {getExtraCharsDisplay(word, i)} - - ); - })} -
-
- ); - return ( <> - +
- {language === ENGLISH_MODE && renderEnglishMode()} + {language === ENGLISH_MODE && ( + + )} {language === CHINESE_MODE && (
Date: Thu, 22 Aug 2024 16:46:36 +0700 Subject: [PATCH 22/28] fix/when should show modal --- src/components/common/SocialLinksModal.js | 38 +++++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/components/common/SocialLinksModal.js b/src/components/common/SocialLinksModal.js index 0186a79..58c559f 100644 --- a/src/components/common/SocialLinksModal.js +++ b/src/components/common/SocialLinksModal.js @@ -66,14 +66,17 @@ const SocialLinksModal = ({ status }) => { // Initial setup and cleanup for countdown timer useEffect(() => { - calculateRemainingTime(); // Initial calculation - const timer = setInterval(calculateRemainingTime, 1000); // Update every second + const now = new Date().getTime(); // Start countdown if not already started if (!localStorage.getItem(COUNTDOWN_KEY)) { startCountdown(); + } else { + calculateRemainingTime(); // Calculate remaining time if countdown already started } + const timer = setInterval(calculateRemainingTime, 1000); // Update every second + return () => clearInterval(timer); // Cleanup on component unmount }, []); @@ -105,17 +108,32 @@ const SocialLinksModal = ({ status }) => { body.style.cursor = "default"; // Reset cursor style on cleanup }; } else { - // Reset countdown and show modal if finished and should show modal - if (status === "finished" && isShouldShowModal) { - setIsShouldShowModal(false); // Hide the modal - localStorage.removeItem(COUNTDOWN_KEY); // Reset countdown start time - localStorage.removeItem(MODAL_DISPLAY_KEY); // Clear modal display timestamp - setRemainingTime(COUNTDOWN_DURATION); // Reset remaining time - } // Ensure cursor is reset if status is not "started" body.style.cursor = "default"; } - }, [status, isShouldShowModal]); + + if (status === "finished") { + // Check and show modal if 5 minutes have passed + const now = new Date().getTime(); + const countdownStartTime = parseInt( + localStorage.getItem(COUNTDOWN_KEY), + 10 + ); + + if ( + countdownStartTime && + now - countdownStartTime >= COUNTDOWN_DURATION + ) { + setIsShouldShowModal(true); + localStorage.setItem(MODAL_DISPLAY_KEY, now.toString()); // Update modal display timestamp + } + + // Reset countdown and modal if finished and should reset + localStorage.removeItem(COUNTDOWN_KEY); // Clear countdown start time + localStorage.removeItem(MODAL_DISPLAY_KEY); // Clear modal display timestamp + setRemainingTime(COUNTDOWN_DURATION); // Reset remaining time + } + }, [status]); // Handle clicks on the overlay const handleOverlayClick = (e) => { From 379c14d89eb194d5d70e25c31d16f2b049d2669d Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Thu, 22 Aug 2024 22:39:03 +0700 Subject: [PATCH 23/28] feat/add performance optimization by rendering necessary words --- .firebase/hosting.YnVpbGQ.cache | 58 +++++----- src/components/common/ChineseModeWords.js | 66 +++++++++++ src/components/common/EnglishModeWords.js | 43 ++++--- src/components/common/SocialLinksModal.js | 2 +- src/components/features/TypeBox/TypeBox.js | 124 ++++++++++----------- 5 files changed, 175 insertions(+), 118 deletions(-) create mode 100644 src/components/common/ChineseModeWords.js diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index 025f15d..f5fdb84 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,29 +1,29 @@ -robots.txt,1724301879504,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec -logo192.png,1724301879503,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 -index.html,1724301986496,f2049e4a03cf6d8d942370013d143e91b50d0d10155f5e3f7592442d26fd8b36 -manifest.json,1724301879503,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c -logo.png,1724301879502,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d -favicon.ico,1724301879502,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 -asset-manifest.json,1724301986529,e42c23fd64865e4d8eadf36f8371816bf50a25904bacbd4017fd74d182d2bd9c -static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1724301986499,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae -logo512.png,1724301879503,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f -static/js/main.04bda0e9.js.LICENSE.txt,1724301986499,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 -static/media/typeSoft.3e307070c0d43972460c.wav,1724301986500,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 -static/js/911.b91b008e.chunk.js,1724301986500,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f -static/js/845.d88f7a05.chunk.js.LICENSE.txt,1724301986499,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e -static/js/845.d88f7a05.chunk.js,1724301986500,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e -static/js/514.489e435b.chunk.js.map,1724301986531,68d375a86e92d47d24bfa1924a8b74f1720d1f15eb0752b69779022b8b7a9e28 -static/js/268.39edc2bf.chunk.js.map,1724301986531,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 -static/js/268.39edc2bf.chunk.js,1724301986500,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba -static/js/110.abadb285.chunk.js.map,1724301986531,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 -static/js/110.abadb285.chunk.js,1724301986500,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 -static/css/main.e6c13ad2.css.map,1724301986500,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 -static/js/514.489e435b.chunk.js,1724301986500,45f4d679735604e53c02fcc5452e77d34d85a662b62088b932d66d4d9435f7ff -static/js/911.b91b008e.chunk.js.map,1724301986531,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 -static/css/main.e6c13ad2.css,1724301986499,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 -static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1724301986500,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 -static/media/keyboard.f9440324ce3fbfc79f5b.wav,1724301986500,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 -static/media/WeChatSupport.d4d0b5b8d76008986829.png,1724301986499,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d -static/js/845.d88f7a05.chunk.js.map,1724301986531,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 -static/js/main.04bda0e9.js,1724301986499,ae6442a8c493260f73bf094844d1970cef9f5849d69668230e0c550ab8f19084 -static/js/main.04bda0e9.js.map,1724301986530,b2cf332901f7e23257a78a30c6ca5f640d3a941951f6215c82345ed879b237f7 +manifest.json,1724337192184,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +logo512.png,1724337192184,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +robots.txt,1724337192184,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +logo192.png,1724337192184,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +logo.png,1724337192184,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +index.html,1724337354174,eba94ff9e21f1cbaa5feb6b80b025b4134a099426052f24b881f147f322a19fc +asset-manifest.json,1724337354227,90d2b1aaaffa343a123bb3a37d0414bd59b996a544c87a48caa643558af4259c +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1724337354181,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +favicon.ico,1724337192181,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +static/js/main.97a30f1f.js.LICENSE.txt,1724337354181,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/js/911.b91b008e.chunk.js.map,1724337354231,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 +static/js/911.b91b008e.chunk.js,1724337354184,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1724337354181,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/845.d88f7a05.chunk.js,1724337354184,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/js/514.489e435b.chunk.js.map,1724337354231,68d375a86e92d47d24bfa1924a8b74f1720d1f15eb0752b69779022b8b7a9e28 +static/js/514.489e435b.chunk.js,1724337354184,45f4d679735604e53c02fcc5452e77d34d85a662b62088b932d66d4d9435f7ff +static/js/268.39edc2bf.chunk.js.map,1724337354231,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 +static/js/268.39edc2bf.chunk.js,1724337354184,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba +static/js/110.abadb285.chunk.js.map,1724337354231,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 +static/js/110.abadb285.chunk.js,1724337354184,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 +static/css/main.e6c13ad2.css.map,1724337354187,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/css/main.e6c13ad2.css,1724337354181,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/media/typeSoft.3e307070c0d43972460c.wav,1724337354181,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1724337354181,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1724337354181,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1724337354181,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/845.d88f7a05.chunk.js.map,1724337354231,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/js/main.97a30f1f.js,1724337354181,983f6120d7c952bedb2c692f8849dd9d364aba3a375cfee05ad3ee417e965411 +static/js/main.97a30f1f.js.map,1724337354227,48d92cc1410ce25a5de188dfe22b94546b79e18b47e81fa2be1583e7c96f5a62 diff --git a/src/components/common/ChineseModeWords.js b/src/components/common/ChineseModeWords.js new file mode 100644 index 0000000..52fe1e5 --- /dev/null +++ b/src/components/common/ChineseModeWords.js @@ -0,0 +1,66 @@ +import React, { memo, useCallback, useRef } from "react"; + +const ChineseModeWords = ({ + currentWords, + currWordIndex, + wordsKey, + isFocusedMode, + status, + wordSpanRefs, + getChineseWordKeyClassName, + getChineseWordClassName, + getCharClassName, + getExtraCharsDisplay, +}) => { + const containerRef = useRef(null); + + // Get word opacity for focus mode + const getWordOpacity = useCallback( + (index) => Math.max(1 - Math.abs(index - currWordIndex) * 0.1, 0.1), + [currWordIndex] + ); + + return ( +
+
+ {currentWords.map((word, i) => { + const opacityValue = isFocusedMode ? getWordOpacity(i) : 1; + + return ( +
+ + {wordsKey[i]} + + + {word.split("").map((char, idx) => ( + + {char} + + ))} + {getExtraCharsDisplay(word, i)} + +
+ ); + })} +
+
+ ); +}; + +export default memo(ChineseModeWords); diff --git a/src/components/common/EnglishModeWords.js b/src/components/common/EnglishModeWords.js index 9d781f4..594c369 100644 --- a/src/components/common/EnglishModeWords.js +++ b/src/components/common/EnglishModeWords.js @@ -1,55 +1,54 @@ -import React, { memo } from "react"; +import React, { memo, useCallback, useRef } from "react"; -// Define the component const EnglishModeWords = ({ - currentWords, currWordIndex, + currentWords, isFocusedMode, status, wordSpanRefs, getWordClassName, getCharClassName, + startIndex, getExtraCharsDisplay, }) => { - const wordMap = {}; + const containerRef = useRef(null); + + // Get word opacity for focus mode + const getWordOpacity = useCallback( + (globalIndex) => + Math.max(1 - Math.abs(globalIndex - currWordIndex) * 0.1, 0.1), + [currWordIndex] + ); return (
{currentWords.map((word, i) => { - // Use a HashMap (object) to store the word by its index - if (!wordMap[i]) { - wordMap[i] = word; - } - - // Process the word immediately using the HashMap - const opacityValue = Math.max( - 1 - Math.abs(i - currWordIndex) * 0.1, - 0.1 - ); + const globalIndex = startIndex + i; return ( - {wordMap[i].split("").map((char, idx) => ( + {word.split("").map((char, idx) => ( {char} ))} - {getExtraCharsDisplay(wordMap[i], i)} + {getExtraCharsDisplay(word, globalIndex)} ); })} diff --git a/src/components/common/SocialLinksModal.js b/src/components/common/SocialLinksModal.js index 58c559f..3b4312e 100644 --- a/src/components/common/SocialLinksModal.js +++ b/src/components/common/SocialLinksModal.js @@ -142,7 +142,7 @@ const SocialLinksModal = ({ status }) => { } }; - if (!isShouldShowModal) { + if (!isShouldShowModal || status !== "finished") { return null; // Do not render anything if the modal should not be shown } diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index c373f0e..06bdd21 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -48,6 +48,7 @@ import { import { SOUND_MAP } from "../sound/sound"; import SocialLinksModal from "../../common/SocialLinksModal"; import EnglishModeWords from "../../common/EnglishModeWords"; +import ChineseModeWords from "../../common/ChineseModeWords"; const TypeBox = ({ textInputRef, @@ -96,8 +97,6 @@ const TypeBox = ({ SYMBOL_ADDON_KEY ); - const [itemsToRender, setItemsToRender] = useState(60); - // Caps Lock const [capsLocked, setCapsLocked] = useState(false); @@ -109,7 +108,6 @@ const TypeBox = ({ if (e.keyCode === 13 || e.keyCode === 9) { e.preventDefault(); setOpenRestart(false); - setItemsToRender(60); reset( countDownConstant, difficulty, @@ -122,7 +120,6 @@ const TypeBox = ({ else if (e.keyCode === 32) { e.preventDefault(); setOpenRestart(false); - setItemsToRender(60); reset( countDownConstant, difficulty, @@ -233,12 +230,13 @@ const TypeBox = ({ setWordsDict((currentArray) => [...currentArray, ...generatedChinese]); } } - if ( - currWordIndex !== 0 && - wordSpanRefs[currWordIndex].current.offsetLeft < - wordSpanRefs[currWordIndex - 1].current.offsetLeft - ) { - wordSpanRefs[currWordIndex - 1].current.scrollIntoView(); + if (wordSpanRefs[currWordIndex]) { + const scrollElement = wordSpanRefs[currWordIndex].current; + if (scrollElement) { + scrollElement.scrollIntoView({ + block: "center", + }); + } } else { return; } @@ -1064,21 +1062,48 @@ const TypeBox = ({ ); }; - const startIndex = 0; - - // Calculate the end index for slicing - const endIndex = startIndex + itemsToRender; + const baseChunkSize = 120; + const [startIndex, setStartIndex] = useState(0); + const [visibleWordsCount, setVisibleWordsCount] = useState(baseChunkSize); - // Get the current slice of words - const currentWords = words.slice(startIndex, endIndex); + // Reset startIndex when status changes + useEffect(() => { + setStartIndex(0); + }, [status]); + // Adjust visible words based on current word index useEffect(() => { - const distanceToEnd = currentWords.length - 1 - currWordIndex; + const endIndex = startIndex + visibleWordsCount; + + // Ensure the current word is within the visible area + if (currWordIndex >= endIndex - 5) { + const newStartIndex = Math.max( + 0, + Math.min( + currWordIndex - Math.floor(visibleWordsCount / 2), + words.length - visibleWordsCount + ) + ); - if (distanceToEnd === 30) { - setItemsToRender((prev) => prev + 30); + if (newStartIndex !== startIndex) { + setStartIndex(newStartIndex); + setVisibleWordsCount( + Math.min(words.length - newStartIndex, baseChunkSize) + ); + } } - }, [currWordIndex]); + }, [currWordIndex, startIndex, words.length, visibleWordsCount]); + + // Calculate the end index and slice the words + const endIndex = useMemo( + () => Math.min(startIndex + visibleWordsCount, words.length), + [startIndex, visibleWordsCount, words.length] + ); + + const currentWords = useMemo( + () => words.slice(startIndex, endIndex), + [startIndex, endIndex, words] + ); return ( <> @@ -1090,6 +1115,7 @@ const TypeBox = ({ currentWords={currentWords} currWordIndex={currWordIndex} isFocusedMode={isFocusedMode} + startIndex={startIndex} status={status} wordSpanRefs={wordSpanRefs} getWordClassName={getWordClassName} @@ -1098,52 +1124,18 @@ const TypeBox = ({ /> )} {language === CHINESE_MODE && ( -
-
- {currentWords.map((word, i) => { - const opacityValue = Math.max( - 1 - Math.abs(i - currWordIndex) * 0.1, - 0.1 - ); - - return ( -
- - {" "} - {wordsKey[i]} - - - {word.split("").map((char, idx) => ( - - {char} - - ))} - {getExtraCharsDisplay(word, i)} - -
- ); - })} -
-
+ )}
Date: Fri, 23 Aug 2024 10:45:04 +0700 Subject: [PATCH 24/28] add ultra zen mode option --- src/App.js | 17 +++++++++++++++ src/components/common/ChineseModeWords.js | 4 ++-- src/components/common/EnglishModeWords.js | 4 ++-- src/components/common/FooterMenu.js | 14 +++++++++++++ src/components/features/TypeBox/TypeBox.js | 5 +++-- src/constants/Constants.js | 24 ++++++++++++++-------- src/style/global.js | 7 +++---- 7 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/App.js b/src/App.js index c4d36cd..1ef1d24 100644 --- a/src/App.js +++ b/src/App.js @@ -66,6 +66,11 @@ function App() { // musicMode setting const [isMusicMode, setIsMusicMode] = useState(false); + // ultraZenMode setting + const [isUltraZenMode, setIsUltraZenMode] = useState( + localStorage.getItem("ultra-zen-mode") === "true" + ); + // coffeeMode setting const [isCoffeeMode, setIsCoffeeMode] = useState(false); @@ -110,6 +115,10 @@ function App() { setIsMusicMode(!isMusicMode); }; + const toggleUltraZenMode = () => { + setIsUltraZenMode(!isUltraZenMode); + }; + const toggleCoffeeMode = () => { setIsCoffeeMode(!isCoffeeMode); setIsTrainerMode(false); @@ -132,6 +141,10 @@ function App() { localStorage.setItem("focused-mode", isFocusedMode); }, [isFocusedMode]); + useEffect(() => { + localStorage.setItem("ultra-zen-mode", isUltraZenMode); + }, [isUltraZenMode]); + const textInputRef = useRef(null); const focusTextInput = () => { textInputRef.current && textInputRef.current.focus(); @@ -181,6 +194,7 @@ function App() { {isWordGameMode && (
{currentWords.map((word, i) => { - const opacityValue = isFocusedMode ? getWordOpacity(i) : 1; + const opacityValue = isUltraZenMode ? getWordOpacity(i) : 1; return (
{ const isSiteInfoDisabled = isMusicMode || isFocusedMode; @@ -171,6 +176,15 @@ const FooterMenu = ({ {WORD_MODE_LABEL} + {isWordGameMode && ( + + + + + + {" "} + + )} { handleGameModeChange(GAME_MODE_SENTENCE); diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 06bdd21..225a46b 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -53,6 +53,7 @@ import ChineseModeWords from "../../common/ChineseModeWords"; const TypeBox = ({ textInputRef, isFocusedMode, + isUltraZenMode, soundMode, soundType, handleInputFocus, @@ -1114,7 +1115,7 @@ const TypeBox = ({ * { opacity: 0; -transition: opacity 500ms ease-in-out; +transition: 500ms; } -.fade-element:hover { +.fade-element:hover > * { opacity: 1; -transition: opacity 500ms ease-in-out; } .primary-stats-title { color: ${({ theme }) => theme.textTypeBox}; From a17af65a3b723fd9e780d8136a492c7336ec7988 Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Fri, 23 Aug 2024 12:15:09 +0700 Subject: [PATCH 25/28] hide modal --- .firebase/hosting.YnVpbGQ.cache | 58 +++++++++++----------- src/components/features/TypeBox/TypeBox.js | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index f5fdb84..19a2fcb 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,29 +1,29 @@ -manifest.json,1724337192184,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c -logo512.png,1724337192184,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f -robots.txt,1724337192184,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec -logo192.png,1724337192184,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 -logo.png,1724337192184,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d -index.html,1724337354174,eba94ff9e21f1cbaa5feb6b80b025b4134a099426052f24b881f147f322a19fc -asset-manifest.json,1724337354227,90d2b1aaaffa343a123bb3a37d0414bd59b996a544c87a48caa643558af4259c -static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1724337354181,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae -favicon.ico,1724337192181,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 -static/js/main.97a30f1f.js.LICENSE.txt,1724337354181,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 -static/js/911.b91b008e.chunk.js.map,1724337354231,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 -static/js/911.b91b008e.chunk.js,1724337354184,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f -static/js/845.d88f7a05.chunk.js.LICENSE.txt,1724337354181,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e -static/js/845.d88f7a05.chunk.js,1724337354184,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e -static/js/514.489e435b.chunk.js.map,1724337354231,68d375a86e92d47d24bfa1924a8b74f1720d1f15eb0752b69779022b8b7a9e28 -static/js/514.489e435b.chunk.js,1724337354184,45f4d679735604e53c02fcc5452e77d34d85a662b62088b932d66d4d9435f7ff -static/js/268.39edc2bf.chunk.js.map,1724337354231,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 -static/js/268.39edc2bf.chunk.js,1724337354184,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba -static/js/110.abadb285.chunk.js.map,1724337354231,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 -static/js/110.abadb285.chunk.js,1724337354184,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 -static/css/main.e6c13ad2.css.map,1724337354187,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 -static/css/main.e6c13ad2.css,1724337354181,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 -static/media/typeSoft.3e307070c0d43972460c.wav,1724337354181,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 -static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1724337354181,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 -static/media/keyboard.f9440324ce3fbfc79f5b.wav,1724337354181,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 -static/media/WeChatSupport.d4d0b5b8d76008986829.png,1724337354181,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d -static/js/845.d88f7a05.chunk.js.map,1724337354231,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 -static/js/main.97a30f1f.js,1724337354181,983f6120d7c952bedb2c692f8849dd9d364aba3a375cfee05ad3ee417e965411 -static/js/main.97a30f1f.js.map,1724337354227,48d92cc1410ce25a5de188dfe22b94546b79e18b47e81fa2be1583e7c96f5a62 +manifest.json,1724384726444,1a9784b8466475389b21e07611f74fb1dd8367643a277a29c503fee0b049140c +robots.txt,1724384726444,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +logo512.png,1724384726444,9ab6cb941e493973baf9755bd3fc76988762e6b514d5bc365aeaccefa0de034f +index.html,1724384851220,512c7d1c4ed690764c74b8a1e8d05fd092a234aa083dbb536c6ee3b71a24f84f +logo.png,1724384726444,fdc50634b1942f229d3b244fd91f8cc141a2fb301cebffecb325d9432f92e35d +logo192.png,1724384726444,50bd60c6fefdd7de57fb652cf717e33c712106ffe0a32da5f75a12bf22cecbc0 +asset-manifest.json,1724384851260,b50ea49fa511bbcddd373781be4c24110f9e5401e6e006c9a274c9c8e40b77d1 +favicon.ico,1724384726440,fc26b72d1f6d47474d8993a667b2af803ede41c9996d0f69e62ed6e415d7fed0 +static/media/discord.b048131aab7bcf46e08115b432eece0a.svg,1724384851220,f5c2d6fa3ec5449e38ba567f81d867468dad78dc9f83c01495f27fce76d735ae +static/media/typeSoft.3e307070c0d43972460c.wav,1724384851220,1dade6a0dc93ff4a2f1658ae9d71850d6a217993e9f008bbdff6124c4f26c3c2 +static/js/main.56113d4e.js.LICENSE.txt,1724384851220,863f9d79659576d4828dd820b2b96ab72da24b2f7b5034e53556f00ba3a08263 +static/js/911.b91b008e.chunk.js.map,1724384851264,fbb41c8f77ac4e7474fc506d4cbff453d6577cfa5101b81a26fa9e42a501f5a5 +static/js/911.b91b008e.chunk.js,1724384851224,9bbb3492a5e45ae74d30a52e06e97ce762b8f36aead1520e2f2e9a148cf1052f +static/js/845.d88f7a05.chunk.js.LICENSE.txt,1724384851224,4be0b6eb0f1a4fd054f85b6a21a7380e9bb7287a99bc268e626a709df6d9a60e +static/js/845.d88f7a05.chunk.js,1724384851224,b92104d5a535577378ceb2317c7babe9d122808be04c04ae04c965eacef5712e +static/js/514.489e435b.chunk.js.map,1724384851264,68d375a86e92d47d24bfa1924a8b74f1720d1f15eb0752b69779022b8b7a9e28 +static/js/514.489e435b.chunk.js,1724384851224,45f4d679735604e53c02fcc5452e77d34d85a662b62088b932d66d4d9435f7ff +static/js/268.39edc2bf.chunk.js.map,1724384851264,67195ad3fc7b932c5be1cdc5652e975194bd639445ce11a0b5a00348a227cd47 +static/js/268.39edc2bf.chunk.js,1724384851224,91b9fbc53657799ac65d53a0505f8d93d5b6171b3e78ef0da74b553fa32172ba +static/js/110.abadb285.chunk.js.map,1724384851264,5296d2a652aba7123952a2bda651109556f9f68bc9a575ffc05762c1b67a7e42 +static/js/110.abadb285.chunk.js,1724384851224,5149fe8447d33c2b6c3c10bd7e0627d205e14e9b47ca558a0a81cf0847324716 +static/css/main.e6c13ad2.css.map,1724384851260,e845e6a28aaddc1902a601eeb9df71ac53bdd9c9a922c6d49febaf4035e42c47 +static/css/main.e6c13ad2.css,1724384851220,20ddccbcb290095bc023db15e46a257ec7d8ee4783d17704654f62d527804f71 +static/media/cherryBlue.f4cad75e8a5792bb3d25.wav,1724384851220,22a637101f4b2aaef7335ed58a20e7db780ae522ef535506c05f1594b5e2b8d2 +static/media/keyboard.f9440324ce3fbfc79f5b.wav,1724384851224,1b248ff6cd5a2ffdc7f24c03f1dba874b01a1c285162a60589a6bb3e17f4b250 +static/media/WeChatSupport.d4d0b5b8d76008986829.png,1724384851220,a2dfd32d2be076a4e5c356ba97001789d403665f3dd90a01a631e9649f2fc32d +static/js/845.d88f7a05.chunk.js.map,1724384851264,acc2fa06363004ca40b27d587ff87677dc97002c7d9a8c52db739433716f8b94 +static/js/main.56113d4e.js.map,1724384851260,0c9b60cb194a8c7e0d91efb47986caabc8f62d407f7c898c9d3ecc5f4087d7b2 +static/js/main.56113d4e.js,1724384851220,127aa2b52cbfe8cdef95584dbf398cdb6d9b65a7cb9ea28ac9cbc4c3e0b3dc90 diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 225a46b..7a5154a 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -1108,7 +1108,7 @@ const TypeBox = ({ return ( <> - + {/* */}
{language === ENGLISH_MODE && ( From adcea1472d45b165805766d4a071a86363ce1cd8 Mon Sep 17 00:00:00 2001 From: Rendi Virgantara Setiawan <143745165+rendi12345678@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:20:36 +0700 Subject: [PATCH 26/28] Update src/components/common/FooterMenu.js Co-authored-by: MUYANGGUO <39578778+MUYANGGUO@users.noreply.github.com> --- src/components/common/FooterMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/FooterMenu.js b/src/components/common/FooterMenu.js index 5a4c52d..6a4c5de 100644 --- a/src/components/common/FooterMenu.js +++ b/src/components/common/FooterMenu.js @@ -180,7 +180,7 @@ const FooterMenu = ({ - + {" "} From f29ac9958e31fb9e0bd8fa7e7482c88984dd658c Mon Sep 17 00:00:00 2001 From: rendi12345678 Date: Fri, 23 Aug 2024 12:48:03 +0700 Subject: [PATCH 27/28] fix/language mode --- src/components/features/TypeBox/Stats.js | 3 +-- src/components/features/TypeBox/TypeBox.js | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/features/TypeBox/Stats.js b/src/components/features/TypeBox/Stats.js index 34ba1c3..af9269d 100644 --- a/src/components/features/TypeBox/Stats.js +++ b/src/components/features/TypeBox/Stats.js @@ -19,6 +19,7 @@ const Stats = ({ countDown, countDownConstant, statsCharCount, + language, rawKeyStrokes, theme, renderResetButton, @@ -56,8 +57,6 @@ const Stats = ({ initialTypingTestHistory ); - const language = localStorage.getItem("language"); - const accuracy = Math.round(statsCharCount[0]); const data = typingTestHistory.map((history) => ({ diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 7a5154a..78e5693 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -1141,6 +1141,7 @@ const TypeBox = ({
Date: Fri, 23 Aug 2024 00:04:44 -0700 Subject: [PATCH 28/28] Merge branch 'add-toggle-focus-mode' of https://github.com/rendi12345678/eletypes-frontend into pr/64 --- src/components/features/TypeBox/TypeBox.js | 4 ++-- src/worker/calculateRawWpmWorker.js | 2 +- src/worker/calculateWpmWorker.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js index 78e5693..8acbb22 100644 --- a/src/components/features/TypeBox/TypeBox.js +++ b/src/components/features/TypeBox/TypeBox.js @@ -414,7 +414,7 @@ const TypeBox = ({ }, []); const calculateWpm = (wpmKeyStrokes, countDownConstant, countDown) => { - if (wpmKeyStrokes !== 0 && countDownConstant - countDown !== 0) { + if (wpmKeyStrokes !== 0) { if (!wpmWorkerRef.current) return; // Ensure worker is initialized wpmWorkerRef.current.postMessage({ @@ -475,7 +475,7 @@ const TypeBox = ({ } // Update stats when typing unless there is no effective WPM - if (wpmKeyStrokes !== 0 && countDownConstant - countDown !== 0) { + if (wpmKeyStrokes !== 0) { calculateWpm(wpmKeyStrokes, countDownConstant, countDown); } diff --git a/src/worker/calculateRawWpmWorker.js b/src/worker/calculateRawWpmWorker.js index 3cf796d..0d3f031 100644 --- a/src/worker/calculateRawWpmWorker.js +++ b/src/worker/calculateRawWpmWorker.js @@ -3,7 +3,7 @@ self.onmessage = function (e) { const { rawKeyStrokes, countDownConstant, countDown } = e.data; const roundedRawWpm = Math.round( - (rawKeyStrokes / 5 / Math.max(countDownConstant - countDown, 1)) * 60.0 + (rawKeyStrokes / 5 / (countDownConstant - countDown + 1)) * 60.0 ); postMessage(roundedRawWpm); diff --git a/src/worker/calculateWpmWorker.js b/src/worker/calculateWpmWorker.js index b9cdbf4..6eba627 100644 --- a/src/worker/calculateWpmWorker.js +++ b/src/worker/calculateWpmWorker.js @@ -3,7 +3,7 @@ self.onmessage = function (e) { const { wpmKeyStrokes, countDownConstant, countDown } = e.data; const currWpm = - (wpmKeyStrokes / 5 / Math.max(countDownConstant - countDown, 1)) * 60.0; + wpmKeyStrokes / 5 / (countDownConstant - countDown + 1) * 60.0; postMessage(currWpm); };