diff --git a/README.md b/README.md
index e1a4173..6c6c76d 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,6 @@ An elegant typing test tool.
https://github.com/gamer-ai/eletype-frontend/issues
-
## Community Channel:
Discord: ![Discord](https://img.shields.io/discord/993567075589181621?style=for-the-badge)
@@ -41,6 +40,9 @@ Discord: ![Discord](https://img.shields.io/discord/993567075589181621?style=for-
- KPM
- Accuracy
- Error analysis (correct/error/missing/extra chars count)
+ - Pacing Style (word pulse/ character caret):
+ - Pulse mode: the active word will have an underlien pulse, which helps improve the speed typing habit.
+ - Caret mode: a pacing caret, advancing character by character, which aligns normal typing habit.
#### 2. Words Card (for English learners)
diff --git a/package-lock.json b/package-lock.json
index 62944a9..7408c26 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "eletype",
- "version": "0.3.0",
+ "version": "0.3.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "eletype",
- "version": "0.3.0",
+ "version": "0.3.1",
"dependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
diff --git a/package.json b/package.json
index e281b4b..9e9f685 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eletype",
- "version": "0.3.0",
+ "version": "0.3.1",
"private": true,
"dependencies": {
"@emotion/react": "^11.9.0",
diff --git a/src/components/features/Keyboard/DefaultKeyboard.js b/src/components/features/Keyboard/DefaultKeyboard.js
index de62d31..1c96bd5 100644
--- a/src/components/features/Keyboard/DefaultKeyboard.js
+++ b/src/components/features/Keyboard/DefaultKeyboard.js
@@ -9,15 +9,16 @@ const DefaultKeyboard = () => {
const [inputChar, setInputChar] = useState("");
const [correctCount, setCorrectCount] = useState(0);
const [incorrectCount, setIncorrectCount] = useState(0);
- const accuracy = (correctCount + incorrectCount) === 0 ? 0 : Math.floor(
- (correctCount / (correctCount + incorrectCount)) * 100
- );
+ const accuracy =
+ correctCount + incorrectCount === 0
+ ? 0
+ : Math.floor((correctCount / (correctCount + incorrectCount)) * 100);
const keys = [..." abcdefghijklmnopqrstuvwxyz "];
const resetStats = () => {
setCorrectCount(0);
setIncorrectCount(0);
};
-
+
useEffect(() => {
keyboardRef.current && keyboardRef.current.focus();
});
@@ -25,7 +26,6 @@ const DefaultKeyboard = () => {
keyboardRef.current && keyboardRef.current.focus();
};
const handleKeyDown = (event) => {
-
setInputChar(event.key);
event.preventDefault();
return;
diff --git a/src/components/features/TypeBox/Stats.js b/src/components/features/TypeBox/Stats.js
index 03f8bfa..10d1a81 100644
--- a/src/components/features/TypeBox/Stats.js
+++ b/src/components/features/TypeBox/Stats.js
@@ -3,46 +3,48 @@ import { Box } from "@mui/system";
import { Tooltip } from "@mui/material";
import { CHAR_TOOLTIP_TITLE } from "../../../constants/Constants";
-const Stats = ({ status, wpm, countDown, countDownConstant, statsCharCount, rawKeyStrokes}) => {
+const Stats = ({
+ status,
+ wpm,
+ countDown,
+ countDownConstant,
+ statsCharCount,
+ rawKeyStrokes,
+}) => {
return (
<>
-
{countDown} s
-
- WPM: {Math.round(wpm)}
- {status === "finished" && (
- Accuracy: {Math.round(statsCharCount[0])} %
- )}
- {status === "finished" && (
-
- {CHAR_TOOLTIP_TITLE}
-
- }
- >
-
- Char:{" "}
- {statsCharCount[1]}/
-
- {statsCharCount[2]}
-
- /{statsCharCount[3]}
- /{statsCharCount[4]}
- /
-
- {statsCharCount[5]}
-
-
-
- )}
- {status === "finished" && (
+ {countDown} s
+
+ WPM: {Math.round(wpm)}
+ {status === "finished" && (
+ Accuracy: {Math.round(statsCharCount[0])} %
+ )}
+ {status === "finished" && (
+
+ {CHAR_TOOLTIP_TITLE}
+
+ }
+ >
- Raw KPM: {Math.round((rawKeyStrokes / countDownConstant) * 60.0)}
+ Char:{" "}
+ {statsCharCount[1]}/
+ {statsCharCount[2]}/
+ {statsCharCount[3]}/
+ {statsCharCount[4]}/
+ {statsCharCount[5]}
- )}
-
+
+ )}
+ {status === "finished" && (
+
+ Raw KPM: {Math.round((rawKeyStrokes / countDownConstant) * 60.0)}
+
+ )}
+
>
);
};
-export default Stats;
\ No newline at end of file
+export default Stats;
diff --git a/src/components/features/TypeBox/TypeBox.js b/src/components/features/TypeBox/TypeBox.js
index cd8904e..314120b 100644
--- a/src/components/features/TypeBox/TypeBox.js
+++ b/src/components/features/TypeBox/TypeBox.js
@@ -13,7 +13,6 @@ import CapsLockSnackbar from "../CapsLockSnackbar";
import Stats from "./Stats";
import { Dialog } from "@mui/material";
import DialogTitle from "@mui/material/DialogTitle";
-
import {
DEFAULT_COUNT_DOWN,
COUNT_DOWN_60,
@@ -30,7 +29,11 @@ import {
CHINESE_MODE_TOOLTIP_TITLE,
DEFAULT_DIFFICULTY_TOOLTIP_TITLE_CHINESE,
HARD_DIFFICULTY_TOOLTIP_TITLE_CHINESE,
- RESTART_BUTTON_TOOLTIP_TITLE
+ RESTART_BUTTON_TOOLTIP_TITLE,
+ PACING_CARET,
+ PACING_PULSE,
+ PACING_CARET_TOOLTIP,
+ PACING_PULSE_TOOLTIP,
} from "../../../constants/Constants";
const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
@@ -40,6 +43,12 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
"timer-constant"
);
+ // local persist pacing style
+ const [pacingStyle, setPacingStyle] = useLocalPersistState(
+ PACING_PULSE,
+ "pacing-style"
+ );
+
// local persist difficulty
const [difficulty, setDifficulty] = useLocalPersistState(
DEFAULT_DIFFICULTY,
@@ -78,7 +87,7 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
return wordsGenerator(DEFAULT_WORDS_COUNT, difficulty, ENGLISH_MODE);
}
if (language === CHINESE_MODE) {
- return chineseWordsGenerator(difficulty ,CHINESE_MODE);
+ return chineseWordsGenerator(difficulty, CHINESE_MODE);
}
});
@@ -133,14 +142,21 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
const [currChar, setCurrChar] = useState("");
useEffect(() => {
- if (currWordIndex === DEFAULT_WORDS_COUNT - 1){
+ if (currWordIndex === DEFAULT_WORDS_COUNT - 1) {
if (language === ENGLISH_MODE) {
- const generatedEng = wordsGenerator(DEFAULT_WORDS_COUNT, difficulty, ENGLISH_MODE);
- setWordsDict(currentArray => [...currentArray, ...generatedEng]);
+ const generatedEng = wordsGenerator(
+ DEFAULT_WORDS_COUNT,
+ difficulty,
+ ENGLISH_MODE
+ );
+ setWordsDict((currentArray) => [...currentArray, ...generatedEng]);
}
if (language === CHINESE_MODE) {
- const generatedChinese = chineseWordsGenerator(difficulty ,CHINESE_MODE);
- setWordsDict(currentArray => [...currentArray, ...generatedChinese]);
+ const generatedChinese = chineseWordsGenerator(
+ difficulty,
+ CHINESE_MODE
+ );
+ setWordsDict((currentArray) => [...currentArray, ...generatedChinese]);
}
}
if (
@@ -289,7 +305,7 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
}
// disable shift alt ctrl
- if (keyCode >= 16 && keyCode <= 18) {
+ if (keyCode >= 16 && keyCode <= 18) {
e.preventDefault();
return;
}
@@ -369,6 +385,13 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
}
};
+ const getExtraCharClassName = (i, idx, extra) => {
+ if (currWordIndex === i && idx === extra.length - 1) {
+ return "caret-extra-char-right-error";
+ }
+ return "error-char";
+ };
+
const getExtraCharsDisplay = (word, i) => {
let input = inputWordsHistory[i];
if (!input) {
@@ -383,7 +406,7 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
const extra = input.slice(word.length, input.length).split("");
history[i] = extra.length;
return extra.map((c, idx) => (
-
+
{c}
));
@@ -407,7 +430,7 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
// reset prevInput to empty (will not go back)
setPrevInput("");
- // here count the space as effective wpm.
+ // here count the space as effective wpm.
setWpmKeyStrokes(wpmKeyStrokes + 1);
return true;
} else {
@@ -426,12 +449,20 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
const getWordClassName = (wordIdx) => {
if (wordsInCorrect.has(wordIdx)) {
if (currWordIndex === wordIdx) {
- return "word error-word active-word";
+ if (pacingStyle === PACING_PULSE) {
+ return "word error-word active-word";
+ } else {
+ return "word error-word active-word-no-pulse";
+ }
}
return "word error-word";
} else {
if (currWordIndex === wordIdx) {
- return "word active-word";
+ if (pacingStyle === PACING_PULSE) {
+ return "word active-word";
+ } else {
+ return "word active-word-no-pulse";
+ }
}
return "word";
}
@@ -454,22 +485,57 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
const getChineseWordClassName = (wordIdx) => {
if (wordsInCorrect.has(wordIdx)) {
if (currWordIndex === wordIdx) {
- return "chinese-word error-word active-word";
+ if (pacingStyle === PACING_PULSE) {
+ return "chinese-word error-word active-word";
+ } else {
+ return "chinese-word error-word active-word-no-pulse";
+ }
}
return "chinese-word error-word";
} else {
if (currWordIndex === wordIdx) {
- return "chinese-word active-word";
+ if (pacingStyle === PACING_PULSE) {
+ return "chinese-word active-word";
+ } else {
+ return "chinese-word active-word-no-pulse";
+ }
}
return "chinese-word";
}
};
- const getCharClassName = (wordIdx, charIdx, char) => {
+
+ const getCharClassName = (wordIdx, charIdx, char, word) => {
const keyString = wordIdx + "." + charIdx;
+ if (
+ pacingStyle === PACING_CARET &&
+ wordIdx === currWordIndex &&
+ charIdx === currCharIndex + 1 &&
+ status !== "finished"
+ ) {
+ return "caret-char-left";
+ }
if (history[keyString] === true) {
+ if (
+ pacingStyle === PACING_CARET &&
+ wordIdx === currWordIndex &&
+ word.length - 1 === currCharIndex &&
+ charIdx === currCharIndex &&
+ status !== "finished"
+ ) {
+ return "caret-char-right-correct";
+ }
return "correct-char";
}
if (history[keyString] === false) {
+ if (
+ pacingStyle === PACING_CARET &&
+ wordIdx === currWordIndex &&
+ word.length - 1 === currCharIndex &&
+ charIdx === currCharIndex &&
+ status !== "finished"
+ ) {
+ return "caret-char-right-error";
+ }
return "error-char";
}
if (
@@ -502,6 +568,13 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
return "inactive-button";
};
+ const getPacingStyleButtonClassName = (buttonPacingStyle) => {
+ if (pacingStyle === buttonPacingStyle) {
+ return "active-button";
+ }
+ return "inactive-button";
+ };
+
const getTimerButtonClassName = (buttonTimerCountDown) => {
if (countDownConstant === buttonTimerCountDown) {
return "active-button";
@@ -531,7 +604,7 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
{word.split("").map((char, idx) => (
{char}
@@ -559,7 +632,7 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
{word.split("").map((char, idx) => (
{char}
@@ -697,6 +770,36 @@ const TypeBox = ({ textInputRef, isFocusedMode, handleInputFocus }) => {
)}
+ {menuEnabled && (
+
+ {
+ setPacingStyle(PACING_PULSE);
+ }}
+ >
+
+
+ {PACING_PULSE}
+
+
+
+ {
+ setPacingStyle(PACING_CARET);
+ }}
+ >
+
+
+ {PACING_CARET}
+
+
+
+
+ )}
diff --git a/src/constants/Constants.js b/src/constants/Constants.js
index 61c0a91..56d9bcd 100644
--- a/src/constants/Constants.js
+++ b/src/constants/Constants.js
@@ -31,10 +31,10 @@ const SUPPORT_TOOLTIP_TITLE =
const AUTHOR = "author: @Muyang Guo\n";
const GITHUB_REPO_LINK = "project: @Github\n";
-const FOCUS_MODE = "focus mode";
+const FOCUS_MODE = "Focus mode";
const MUSIC_MODE =
- "spotify player. You will need to login spotify first to use the full feature.";
+ "Spotify player. You will need to login spotify first to use the full feature.";
const FREE_MODE =
"Free typing mode\nType any thing, no pressure, it's coffee time! \n ";
@@ -55,7 +55,13 @@ const FIFTEEN_SENTENCES_COUNT = 15;
const ENGLISH_SENTENCE_MODE_TOOLTIP_TITLE = "English Sentence Mode";
const CHINESE_SENTENCE_MODE_TOOLTIP_TITLE = "Chinese Sentence Mode";
-const WORDS_CARD_MODE = "Words Card Mode, learn something in typing!"
+const WORDS_CARD_MODE = "Words Card mode, learn something in typing!"
+
+const PACING_CARET = "caret";
+const PACING_PULSE = "pulse";
+
+const PACING_CARET_TOOLTIP = "type the word with a caret \"|\" , character by character.";
+const PACING_PULSE_TOOLTIP = "type the word with a pulse \"____\", this helps improving wpm and your speed typing pace habit.";
export {
DEFAULT_WORDS_COUNT,
@@ -97,5 +103,9 @@ export {
WORDS_CARD_MODE,
RESTART_BUTTON_TOOLTIP_TITLE_WORDSCARD,
SELECT_ONE_OR_MORE_CHAPTERS,
- RECITE_MODE_TITLE
+ RECITE_MODE_TITLE,
+ PACING_CARET,
+ PACING_PULSE,
+ PACING_CARET_TOOLTIP,
+ PACING_PULSE_TOOLTIP
};
diff --git a/src/style/global.js b/src/style/global.js
index 2e7dfad..7fef4b4 100644
--- a/src/style/global.js
+++ b/src/style/global.js
@@ -178,29 +178,75 @@ export const GlobalStyles = createGlobalStyle`
}
.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};}
- }
+ 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{
- padding-right: 1px;
+ 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};
- padding-right: 1px;
}
.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;
- padding-right: 1px;
+ border-right: 1px solid ${({ theme }) => theme.stats};
+ border-left: 1px solid transparent;
}
.hidden-input{