refactor: custom text logic and modal (#5327)

This commit is contained in:
Jack 2024-04-23 16:58:00 +02:00 committed by GitHub
parent 727ff93611
commit ccc9a39a99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 706 additions and 578 deletions

View file

@ -23,10 +23,12 @@ const RESULT_SCHEMA = joi
consistency: joi.number().min(0).max(100).required(),
customText: joi.object({
textLen: joi.number().required(),
isWordRandom: joi.boolean().required(),
isTimeRandom: joi.boolean().required(),
word: joi.number().allow(null),
time: joi.number().allow(null),
mode: joi.string().valid("repeat", "random").required(),
pipeDelimiter: joi.boolean().required(),
limit: joi.object({
mode: joi.string().valid("word", "time", "section").required(),
value: joi.number().min(0).required(),
}),
}),
difficulty: joi.string().valid("normal", "expert", "master").required(),
funbox: joi

View file

@ -84,20 +84,16 @@ export function isTestTooShort(result: SharedTypes.CompletedEvent): boolean {
if (mode === "custom") {
if (!customText) return true;
const { isWordRandom, isTimeRandom, textLen, word, time } = customText;
const setTextTooShort =
!isWordRandom && !isTimeRandom && _.isNumber(textLen) && textLen < 10;
const randomWordsTooShort = isWordRandom && !isTimeRandom && word < 10;
const randomTimeTooShort = !isWordRandom && isTimeRandom && time < 15;
const wordLimitTooShort =
(customText.limit.mode === "word" ||
customText.limit.mode === "section") &&
customText.limit.value < 10;
const timeLimitTooShort =
customText.limit.mode === "time" && customText.limit.value < 15;
const bailedOutTooShort = bailedOut
? bailedOut && testDuration < 15
: false;
return (
setTextTooShort ||
randomWordsTooShort ||
randomTimeTooShort ||
bailedOutTooShort
);
return wordLimitTooShort || timeLimitTooShort || bailedOutTooShort;
}
if (mode === "zen") {

View file

@ -372,20 +372,20 @@
<div class="buttonsTop">
<div class="button saveCustomText">
<i class="fas fa-save"></i>
Save
save
</div>
<div class="button showSavedTexts">
<i class="fas fa-folder"></i>
Show saved texts
saved texts
</div>
<input id="fileInput" type="file" class="hidden" accept=".txt" />
<label for="fileInput" class="button importText">
<i class="fas fa-file-import"></i>
Open file
open file
</label>
<div class="button wordfilter">
<i class="fas fa-filter"></i>
Words filter
words filter
</div>
</div>
<div class="savedTexts hidden" style="display: none">
@ -405,70 +405,133 @@
<p class="small">Click anywhere to start editing the text.</p>
</div>
</div>
<div class="challengeWarning">
<div>
<p>
A challenge is currently loaded.
<br />
Editing the settings will clear the challenge.
<br />
<br />
</p>
<p class="small">Click anywhere to edit.</p>
</div>
</div>
<textarea class="textarea"></textarea>
</div>
<div class="inputs">
<label class="checkboxWithSub randomWordsCheckbox">
<input type="checkbox" />
<div>Random</div>
<div class="sub">
Randomize the above words, and control how many words are generated.
<div class="group" data-id="mode">
<div class="title">
<i class="fas fa-fw fa-cog"></i>
Mode
</div>
<div class="sub">Change the way words are generated.</div>
<div class="groupInputs">
<div class="buttonGroup">
<button value="simple">simple</button>
<button value="repeat">repeat</button>
<button value="random">random</button>
</div>
</div>
</label>
<div class="randomInputFields disabled">
<label class="wordcount">
Words
<input type="number" value="" min="1" max="10000" />
</label>
<label class="sectioncount hidden">
Sections
<input type="number" value="" min="1" max="10000" />
</label>
<div style="color: var(--sub-color)">or</div>
<label class="time">
Time
<input type="number" value="" min="1" max="10000" />
</label>
<!-- <div style="color: var(--sub-color)">or</div> -->
</div>
<label class="checkboxWithSub typographyCheck">
<input type="checkbox" checked />
<div>Remove fancy typography</div>
<div class="group" data-id="limit">
<div class="title">
<i class="fas fa-fw fa-step-forward"></i>
Limit
</div>
<div class="sub">
Control how many words to generate or for how long you want to type.
</div>
<div class="groupInputs limitInputs">
<input
class="words"
type="number"
value=""
min="0"
placeholder="words"
/>
<input
class="sections hidden"
type="number"
value=""
min="0"
placeholder="sections"
/>
<div class="or">or</div>
<input
class="time"
type="number"
value=""
min="0"
placeholder="time"
/>
</div>
</div>
<div class="group" data-id="fancy">
<div class="title">
<i class="fas fa-fw fa-pen-fancy"></i>
Remove fancy typography
</div>
<div class="sub">
Standardises typography symbols (for example “ and ” become ")
</div>
</label>
<label class="checkboxWithSub replaceControlCharacters">
<input type="checkbox" checked />
<div>Replace control characters</div>
<div class="groupInputs">
<div class="buttonGroup">
<button value="false">no</button>
<button value="true">yes</button>
</div>
</div>
</div>
<div class="group" data-id="control">
<div class="title">
<i class="fas fa-fw fa-code"></i>
Replace control characters
</div>
<div class="sub">
Replace control characters (\n becomes a new line and \t becomes a
tab)
</div>
</label>
<label class="checkboxWithSub delimiterCheck">
<input type="checkbox" />
<div>Pipe delimiter</div>
<div class="groupInputs">
<div class="buttonGroup">
<button value="false">no</button>
<button value="true">yes</button>
</div>
</div>
</div>
<div class="group" data-id="delimiter">
<div class="title">
<i class="fas fa-fw fa-grip-lines-vertical"></i>
Word delimiter
</div>
<div class="sub">
Change how words are separated. Using the pipe delimiter allows you to
randomize groups of words.
</div>
</label>
<label class="checkboxWithSub replaceNewlineWithSpace">
<input type="checkbox" />
<div>Replace new lines with spaces</div>
<div class="groupInputs">
<div class="buttonGroup">
<button value="true">pipe</button>
<button value="false">space</button>
</div>
</div>
</div>
<div class="group" data-id="newlines">
<div class="title">
<i class="fas fa-fw fa-level-down-alt fa-rotate-90"></i>
Replace new lines with spaces
</div>
<div class="sub">
Replace all new line characters with spaces. Can automatically add
periods to the end of lines if you wish.
</div>
</label>
<div class="replaceNewLinesButtons disabled">
<div class="buttonGroup">
<div class="button active noPeriods" data-replace-new-lines="space">
no periods
</div>
<div class="button periods" data-replace-new-lines="period">
periods
<div class="groupInputs">
<div class="buttonGroup">
<button value="off">off</button>
<button value="space">space</button>
<button value="periodSpace">period + space</button>
</div>
</div>
</div>

View file

@ -53,11 +53,16 @@
#customTextModal {
.modal {
max-width: 1200px;
// grid-template-areas:
// "topButtons topButtons topButtons"
// "textArea textArea checkboxes"
// "ok ok ok";
grid-template-areas:
"topButtons topButtons topButtons"
"topButtons topButtons checkboxes"
"textArea textArea checkboxes"
"ok ok ok";
"ok ok checkboxes";
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: min-content 1fr min-content;
.buttonsTop {
grid-area: topButtons;
@ -75,32 +80,23 @@
grid-area: ok;
}
.replaceNewLinesButtons {
display: grid;
justify-content: center;
width: 100%;
font-size: 0.75rem;
grid-template-columns: 1fr;
padding: 0 1rem;
.buttonGroup {
display: grid;
grid-template-columns: 1fr 1fr;
.button:first-child {
border-radius: var(--roundness) 0 0 var(--roundness);
}
.button:last-child {
border-radius: 0 var(--roundness) var(--roundness) 0;
}
}
&.disabled {
opacity: 0.5;
pointer-events: none;
-webkit-user-select: none;
user-select: none;
}
}
// .replaceNewLinesButtons {
// display: grid;
// justify-content: center;
// width: 100%;
// font-size: 0.75rem;
// grid-template-columns: 1fr;
// padding: 0 1rem;
// &.disabled {
// opacity: 0.5;
// pointer-events: none;
// -webkit-user-select: none;
// user-select: none;
// }
// }
.longCustomTextWarning {
.longCustomTextWarning,
.challengeWarning {
// background: rgba(0, 0, 0, 0.5);
background: var(--sub-alt-color);
position: absolute;
@ -126,6 +122,7 @@
.buttonsTop {
display: grid;
// grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 1rem;
}
@ -144,6 +141,7 @@
}
textarea {
align-self: start;
background: var(--sub-alt-color);
padding: 1rem;
color: var(--main-color);
@ -154,7 +152,7 @@
width: 100%;
border-radius: var(--roundness);
resize: vertical;
height: 450px;
min-height: 477px;
color: var(--text-color);
overflow-x: hidden;
overflow-y: scroll;
@ -167,28 +165,71 @@
align-items: center;
justify-items: left;
height: min-content;
margin: 1rem 0;
&.disabled {
opacity: 0.5;
-webkit-user-select: none;
user-select: none;
pointer-events: none;
}
}
.randomInputFields {
// margin: 1rem 0;
font-size: 0.75rem;
display: grid;
grid-template-columns: 1fr auto 1fr;
text-align: center;
align-items: center;
width: 100%;
gap: 1rem;
.group {
display: grid;
// gap: 0.5rem;
align-items: center;
width: 100%;
.title {
color: var(--sub-color);
text-transform: lowercase;
}
.sub {
// font-size: 0.75em;
// height: 0;
// overflow: hidden;
color: var(--text-color);
margin-top: 0.25rem;
margin-bottom: 0.5rem;
}
.groupInputs {
&.limitInputs {
grid-column: 2/-1;
display: flex;
grid-template-columns: 1fr auto 1fr;
text-align: center;
align-items: center;
width: 100%;
gap: 1rem;
.or {
color: var(--sub-color);
}
&.disabled {
opacity: 0.5;
pointer-events: none;
-webkit-user-select: none;
user-select: none;
}
}
.buttonGroup {
display: flex;
width: 100%;
gap: 0.5rem;
button {
flex-grow: 1;
}
// button {
// flex-grow: 1;
// border-radius: 0;
// }
// button:first-child {
// border-radius: var(--roundness) 0 0 var(--roundness);
// }
// button:last-child {
// border-radius: 0 var(--roundness) var(--roundness) 0;
// }
}
}
}
&.disabled {
opacity: 0.5;
pointer-events: none;
-webkit-user-select: none;
user-select: none;
pointer-events: none;
}
}
}

View file

@ -39,13 +39,13 @@
#app {
grid-template-columns: auto;
}
#customTextModal {
padding: 2rem;
.modal {
grid-template-columns: 1fr 1fr 2fr;
width: 100%;
}
}
// #customTextModal {
// padding: 2rem;
// .modal {
// grid-template-columns: 1fr 1fr 2fr;
// width: 100%;
// }
// }
}
//1250px
@ -57,8 +57,13 @@
}
}
}
#customTextModal .modal .buttonsTop {
grid-template-columns: 1fr 1fr;
#customTextModal .modal {
textarea {
min-height: 426px;
}
.buttonsTop {
grid-template-columns: 1fr 1fr;
}
}
}

View file

@ -8,15 +8,14 @@ function canBailOut(): boolean {
return (
(Config.mode === "custom" && CustomTextState.isCustomTextLong() === true) ||
(Config.mode === "custom" &&
CustomText.isWordRandom &&
(CustomText.word >= 5000 || CustomText.word === 0)) ||
(CustomText.getLimitMode() === "word" ||
CustomText.getLimitMode() === "section") &&
(CustomText.getLimit().value >= 5000 ||
CustomText.getLimit().value === 0)) ||
(Config.mode === "custom" &&
!CustomText.isWordRandom &&
!CustomText.isTimeRandom &&
CustomText.text.length >= 5000) ||
(Config.mode === "custom" &&
CustomText.isTimeRandom &&
(CustomText.time >= 3600 || CustomText.time === 0)) ||
CustomText.getLimitMode() === "time" &&
(CustomText.getLimitValue() >= 3600 ||
CustomText.getLimitValue() === 0)) ||
(Config.mode === "words" && Config.words >= 5000) ||
Config.words === 0 ||
(Config.mode === "time" && (Config.time >= 3600 || Config.time === 0)) ||

View file

@ -248,13 +248,13 @@ export async function setup(challengeName: string): Promise<boolean> {
UpdateConfig.setMode("words", true);
UpdateConfig.setDifficulty("normal", true);
} else if (challenge.type === "customText") {
CustomText.setDelimiter(" ");
CustomText.setText((challenge.parameters[0] as string).split(" "));
CustomText.setIsTimeRandom(false);
CustomText.setIsSectionRandom(false);
CustomText.setIsWordRandom(challenge.parameters[1] as boolean);
CustomText.setWord(challenge.parameters[2] as number);
CustomText.setTime(-1);
CustomText.setMode(challenge.parameters[1] as SharedTypes.CustomTextMode);
CustomText.setLimitValue(challenge.parameters[2] as number);
CustomText.setLimitMode(
challenge.parameters[3] as SharedTypes.CustomTextLimitMode
);
CustomText.setPipeDelimiter(challenge.parameters[4] as boolean);
UpdateConfig.setMode("custom", true);
UpdateConfig.setDifficulty("normal", true);
} else if (challenge.type === "script") {
@ -268,13 +268,10 @@ export async function setup(challengeName: string): Promise<boolean> {
let text = scriptdata.trim();
text = text.replace(/[\n\r\t ]/gm, " ");
text = text.replace(/ +/gm, " ");
CustomText.setDelimiter(" ");
CustomText.setText(text.split(" "));
CustomText.setIsWordRandom(false);
CustomText.setIsSectionRandom(false);
CustomText.setIsTimeRandom(false);
CustomText.setTime(-1);
CustomText.setWord(-1);
CustomText.setMode("repeat");
CustomText.setLimitMode("word");
CustomText.setPipeDelimiter(false);
UpdateConfig.setMode("custom", true);
UpdateConfig.setDifficulty("normal", true);
if (challenge.parameters[1] !== null) {

View file

@ -312,8 +312,8 @@ function handleSpace(): void {
if (
!Config.showAllLines ||
Config.mode === "time" ||
(CustomText.isWordRandom && CustomText.word === 0) ||
CustomText.isTimeRandom
(Config.mode === "custom" && CustomText.getLimitValue() === 0) ||
(Config.mode === "custom" && CustomText.getLimitMode() === "time")
) {
const currentTop: number = Math.floor(
document.querySelectorAll<HTMLElement>("#words .word")[
@ -1042,7 +1042,7 @@ $(document).on("keydown", async (event) => {
Config.mode,
Config.words,
Config.time,
CustomText,
CustomText.getData(),
CustomTextState.isCustomTextLong() ?? false
)
) {

View file

@ -15,82 +15,119 @@ const popup = "#customTextModal .modal";
type State = {
textarea: string;
lastSavedTextareaState: string;
longCustomTextWarning: boolean;
randomWordsChecked: boolean;
randomWordInputs: {
challengeWarning: boolean;
customTextMode: "simple" | SharedTypes.CustomTextMode;
customTextLimits: {
word: string;
time: string;
section: string;
};
pipeDelimiterChecked: boolean;
replaceNewlines: "off" | "space" | "period";
replaceControlCharactersChecked: boolean;
replaceFancyTypographyChecked: boolean;
removeFancyTypographyEnabled: boolean;
replaceControlCharactersEnabled: boolean;
customTextPipeDelimiter: boolean;
replaceNewlines: "off" | "space" | "periodSpace";
};
const state: State = {
textarea: CustomText.text.join(" "),
lastSavedTextareaState: CustomText.text.join(" "),
textarea: CustomText.getText().join(
CustomText.getPipeDelimiter() ? "|" : " "
),
longCustomTextWarning: false,
randomWordsChecked: false,
randomWordInputs: {
challengeWarning: false,
customTextMode: "simple",
customTextLimits: {
word: "",
time: "",
section: "",
},
pipeDelimiterChecked: false,
removeFancyTypographyEnabled: true,
replaceControlCharactersEnabled: true,
customTextPipeDelimiter: false,
replaceNewlines: "off",
replaceControlCharactersChecked: true,
replaceFancyTypographyChecked: true,
};
function updateUI(): void {
if (state.randomWordsChecked) {
$(`${popup} .randomWordsCheckbox input`).prop("checked", true);
$(`${popup} .inputs .randomInputFields`).removeClass("disabled");
} else {
$(`${popup} .randomWordsCheckbox input`).prop("checked", false);
$(`${popup} .inputs .randomInputFields`).addClass("disabled");
$(`${popup} .inputs .group[data-id="mode"] button`).removeClass("active");
$(
`${popup} .inputs .group[data-id="mode"] button[value="${state.customTextMode}"]`
).addClass("active");
$(`${popup} .inputs .group[data-id="limit"] input.words`).addClass("hidden");
$(`${popup} .inputs .group[data-id="limit"] input.sections`).addClass(
"hidden"
);
$(`${popup} .inputs .group[data-id="limit"] input.words`).val(
state.customTextLimits.word
);
$(`${popup} .inputs .group[data-id="limit"] input.time`).val(
state.customTextLimits.time
);
$(`${popup} .inputs .group[data-id="limit"] input.sections`).val(
state.customTextLimits.section
);
if (state.customTextLimits.word !== "") {
$(`${popup} .inputs .group[data-id="limit"] input.words`).removeClass(
"hidden"
);
}
if (state.customTextLimits.section !== "") {
$(`${popup} .inputs .group[data-id="limit"] input.sections`).removeClass(
"hidden"
);
}
if (state.pipeDelimiterChecked) {
$(`${popup} .delimiterCheck input`).prop("checked", true);
if (state.customTextPipeDelimiter) {
$(`${popup} .inputs .group[data-id="limit"] input.sections`).removeClass(
"hidden"
);
$(`${popup} .inputs .group[data-id="limit"] input.words`).addClass(
"hidden"
);
} else {
$(`${popup} .delimiterCheck input`).prop("checked", false);
$(`${popup} .inputs .group[data-id="limit"] input.words`).removeClass(
"hidden"
);
$(`${popup} .inputs .group[data-id="limit"] input.sections`).addClass(
"hidden"
);
}
$(`${popup} .replaceNewLinesButtons .button`).removeClass("active");
if (state.replaceNewlines !== "off") {
$(`${popup} .inputs .replaceNewLinesButtons`).removeClass("disabled");
$(
`${popup} .replaceNewLinesButtons .button[data-replace-new-lines=${state.replaceNewlines}]`
).addClass("active");
if (state.customTextMode === "simple") {
$(`${popup} .inputs .group[data-id="limit"]`).addClass("disabled");
$(`${popup} .inputs .group[data-id="limit"] input`).val("");
$(`${popup} .inputs .group[data-id="limit"] input`).prop("disabled", true);
} else {
$(`${popup} .inputs .replaceNewLinesButtons`).addClass("disabled");
$(`${popup} .inputs .group[data-id="limit"]`).removeClass("disabled");
$(`${popup} .inputs .group[data-id="limit"] input`).prop("disabled", false);
}
$(`${popup} .inputs .group[data-id="fancy"] button`).removeClass("active");
$(
`${popup} .inputs .group[data-id="fancy"] button[value="${state.removeFancyTypographyEnabled}"]`
).addClass("active");
$(`${popup} .inputs .group[data-id="control"] button`).removeClass("active");
$(
`${popup} .inputs .group[data-id="control"] button[value="${state.replaceControlCharactersEnabled}"]`
).addClass("active");
$(`${popup} .inputs .group[data-id="delimiter"] button`).removeClass(
"active"
);
$(
`${popup} .inputs .group[data-id="delimiter"] button[value="${state.customTextPipeDelimiter}"]`
).addClass("active");
$(`${popup} .inputs .group[data-id="newlines"] button`).removeClass("active");
$(
`${popup} .inputs .group[data-id="newlines"] button[value="${state.replaceNewlines}"]`
).addClass("active");
$(`${popup} textarea`).val(state.textarea);
if (state.pipeDelimiterChecked) {
$(`${popup} .randomInputFields .sectioncount `).removeClass("hidden");
state.randomWordInputs.word = "";
$(`${popup} .randomInputFields .wordcount `).addClass("hidden");
} else {
state.randomWordInputs.section = "";
$(`${popup} .randomInputFields .sectioncount `).addClass("hidden");
$(`${popup} .randomInputFields .wordcount `).removeClass("hidden");
}
$(`${popup} .randomInputFields .wordcount input`).val(
state.randomWordInputs.word
);
$(`${popup} .randomInputFields .time input`).val(state.randomWordInputs.time);
$(`${popup} .randomInputFields .sectioncount input`).val(
state.randomWordInputs.section
);
if (state.longCustomTextWarning) {
$(`${popup} .longCustomTextWarning`).removeClass("hidden");
$(`${popup} .randomWordsCheckbox input`).prop("checked", false);
@ -102,25 +139,48 @@ function updateUI(): void {
$(`${popup} .longCustomTextWarning`).addClass("hidden");
$(`${popup} .inputs`).removeClass("disabled");
}
if (state.challengeWarning) {
$(`${popup} .challengeWarning`).removeClass("hidden");
$(`${popup} .randomWordsCheckbox input`).prop("checked", false);
$(`${popup} .delimiterCheck input`).prop("checked", false);
$(`${popup} .typographyCheck`).prop("checked", true);
$(`${popup} .replaceNewlineWithSpace input`).prop("checked", false);
$(`${popup} .inputs`).addClass("disabled");
} else {
$(`${popup} .challengeWarning`).addClass("hidden");
$(`${popup} .inputs`).removeClass("disabled");
}
}
async function beforeAnimation(
modalEl: HTMLElement,
modalChainData?: IncomingData
): Promise<void> {
state.longCustomTextWarning = CustomTextState.isCustomTextLong() ?? false;
state.randomWordsChecked =
CustomText.isSectionRandom ||
CustomText.isTimeRandom ||
CustomText.isWordRandom;
state.pipeDelimiterChecked = CustomText.delimiter === "|";
// state.replaceNewlinesChecked = false;
state.customTextMode = CustomText.getMode();
if (CustomTextState.isCustomTextLong()) {
// if we are in long custom text mode, always reset the textarea state to the current text
state.textarea = CustomText.text.join(" ");
if (
state.customTextMode === "repeat" &&
CustomText.getLimitMode() === "word" &&
CustomText.getLimitValue() === CustomText.getText().length
) {
state.customTextMode = "simple";
}
state.customTextLimits.word = "";
state.customTextLimits.time = "";
state.customTextLimits.section = "";
if (CustomText.getLimitMode() === "word") {
state.customTextLimits.word = `${CustomText.getLimitValue()}`;
} else if (CustomText.getLimitMode() === "time") {
state.customTextLimits.time = `${CustomText.getLimitValue()}`;
} else if (CustomText.getLimitMode() === "section") {
state.customTextLimits.section = `${CustomText.getLimitValue()}`;
}
state.customTextPipeDelimiter = CustomText.getPipeDelimiter();
state.longCustomTextWarning = CustomTextState.isCustomTextLong() ?? false;
if (modalChainData?.text !== undefined) {
if (modalChainData.long !== true && CustomTextState.isCustomTextLong()) {
CustomTextState.setCustomTextName("", undefined);
@ -135,26 +195,25 @@ async function beforeAnimation(
? modalChainData.text
: state.textarea + " " + modalChainData.text;
state.textarea = newText;
state.customTextMode = "simple";
state.customTextLimits.word = `${cleanUpText().length}`;
state.customTextLimits.time = "";
state.customTextLimits.section = "";
}
state.randomWordInputs.word =
CustomText.word === -1 ? "" : `${CustomText.word}`;
state.randomWordInputs.time =
CustomText.time === -1 ? "" : `${CustomText.time}`;
state.randomWordInputs.section =
CustomText.section === -1 ? "" : `${CustomText.section}`;
updateUI();
}
async function afterAnimation(): Promise<void> {
if (!CustomTextState.isCustomTextLong()) {
if (!state.challengeWarning && !state.longCustomTextWarning) {
$(`${popup} textarea`).trigger("focus");
}
}
export function show(showOptions?: ShowOptions): void {
state.textarea = state.lastSavedTextareaState;
state.textarea = CustomText.getText().join(
CustomText.getPipeDelimiter() ? "|" : " "
);
void modal.show({
...(showOptions as ShowOptions<IncomingData>),
beforeAnimation,
@ -207,7 +266,7 @@ function cleanUpText(): string[] {
//replace zero width characters
text = text.replace(/[\u200B-\u200D\u2060\uFEFF]/g, "");
if (state.replaceControlCharactersChecked) {
if (state.replaceControlCharactersEnabled) {
text = text.replace(/([^\\]|^)\\t/gm, "$1\t");
text = text.replace(/([^\\]|^)\\n/gm, "$1\n");
text = text.replace(/\\\\t/gm, "\\t");
@ -216,12 +275,12 @@ function cleanUpText(): string[] {
text = text.replace(/ +/gm, " ");
text = text.replace(/( *(\r\n|\r|\n) *)/g, "\n ");
if (state.replaceFancyTypographyChecked) {
if (state.removeFancyTypographyEnabled) {
text = Misc.cleanTypographySymbols(text);
}
if (state.replaceNewlines !== "off") {
const periods = state.replaceNewlines === "period";
const periods = state.replaceNewlines === "periodSpace";
if (periods) {
text = text.replace(/\n/gm, ". ");
text = text.replace(/\.\. /gm, ". ");
@ -232,7 +291,9 @@ function cleanUpText(): string[] {
}
}
const words = text.split(CustomText.delimiter).filter((word) => word !== "");
const words = text
.split(state.customTextPipeDelimiter ? "|" : " ")
.filter((word) => word !== "");
return words;
}
@ -242,57 +303,38 @@ function apply(): void {
return;
}
state.lastSavedTextareaState = state.textarea;
CustomText.setText(cleanUpText());
CustomText.setWord(parseInt(state.randomWordInputs.word || "-1"));
CustomText.setTime(parseInt(state.randomWordInputs.time || "-1"));
CustomText.setSection(parseInt(state.randomWordInputs.section || "-1"));
CustomText.setIsWordRandom(state.randomWordsChecked && CustomText.word > -1);
CustomText.setIsTimeRandom(state.randomWordsChecked && CustomText.time > -1);
CustomText.setIsSectionRandom(
state.randomWordsChecked && CustomText.section > -1
);
if (
state.randomWordsChecked &&
!CustomText.isTimeRandom &&
!CustomText.isWordRandom &&
!CustomText.isSectionRandom
[
state.customTextLimits.word,
state.customTextLimits.time,
state.customTextLimits.section,
].filter((limit) => limit !== "").length > 1
) {
Notifications.add(
"You need to specify word count or time in seconds to start a random custom test",
0,
{
duration: 5,
}
);
Notifications.add("You can only specify one limit", 0, {
duration: 5,
});
return;
}
if (
// ($(`${popup} .randomWordsCheckbox input`).prop("checked") as boolean) &&
state.randomWordsChecked &&
CustomText.isTimeRandom &&
CustomText.isWordRandom
state.customTextMode !== "simple" &&
state.customTextLimits.word === "" &&
state.customTextLimits.time === "" &&
state.customTextLimits.section === ""
) {
Notifications.add(
"You need to pick between word count or time in seconds to start a random custom test",
0,
{
duration: 5,
}
);
Notifications.add("You need to specify a limit", 0, {
duration: 5,
});
return;
}
if (
(CustomText.isWordRandom && CustomText.word === 0) ||
(CustomText.isTimeRandom && CustomText.time === 0)
state.customTextLimits.section === "0" ||
state.customTextLimits.word === "0" ||
state.customTextLimits.time === "0"
) {
Notifications.add(
"Infinite words! Make sure to use Bail Out from the command line to save your result.",
"Infinite test! Make sure to use Bail Out from the command line to save your result.",
0,
{
duration: 7,
@ -300,6 +342,28 @@ function apply(): void {
);
}
const text = cleanUpText();
if (state.customTextMode === "simple") {
CustomText.setMode("repeat");
} else {
CustomText.setMode(state.customTextMode);
}
CustomText.setPipeDelimiter(state.customTextPipeDelimiter);
CustomText.setText(text);
if (state.customTextLimits.word !== "") {
CustomText.setLimitMode("word");
CustomText.setLimitValue(parseInt(state.customTextLimits.word));
} else if (state.customTextLimits.time !== "") {
CustomText.setLimitMode("time");
CustomText.setLimitValue(parseInt(state.customTextLimits.time));
} else if (state.customTextLimits.section !== "") {
CustomText.setLimitMode("section");
CustomText.setLimitValue(parseInt(state.customTextLimits.section));
}
ChallengeController.clearActive();
ManualRestart.set();
if (Config.mode !== "custom") UpdateConfig.setMode("custom");
@ -307,67 +371,115 @@ function apply(): void {
hide();
}
function handleDelimiterChange(): void {
let newtext = state.textarea
.split(state.customTextPipeDelimiter ? " " : "|")
.join(state.customTextPipeDelimiter ? "|" : " ");
newtext = newtext.replace(/\n /g, "\n");
state.textarea = newtext;
}
async function setup(modalEl: HTMLElement): Promise<void> {
modalEl
.querySelector("#fileInput")
?.addEventListener("change", handleFileOpen);
modalEl
.querySelector(".randomWordsCheckbox input")
?.addEventListener("change", (e) => {
state.randomWordsChecked = (e.target as HTMLInputElement).checked;
updateUI();
});
modalEl
.querySelector(".typographyCheck input")
?.addEventListener("change", (e) => {
state.replaceFancyTypographyChecked = (
e.target as HTMLInputElement
).checked;
updateUI();
});
modalEl
.querySelector(".delimiterCheck input")
?.addEventListener("change", (e) => {
state.pipeDelimiterChecked = (e.target as HTMLInputElement).checked;
if (state.textarea !== CustomText.text.join(CustomText.delimiter)) {
const currentTextSplit = state.textarea.split(CustomText.delimiter);
let newtext = currentTextSplit.join(
state.pipeDelimiterChecked ? "|" : " "
);
newtext = newtext.replace(/\n /g, "\n");
state.textarea = newtext;
} else {
let newtext = CustomText.text.join(
state.pipeDelimiterChecked ? "|" : " "
);
newtext = newtext.replace(/\n /g, "\n");
state.textarea = newtext;
}
CustomText.setDelimiter(state.pipeDelimiterChecked ? "|" : " ");
updateUI();
});
modalEl
.querySelector(".replaceNewlineWithSpace input")
?.addEventListener("change", (e) => {
const checked = (e.target as HTMLInputElement).checked;
if (checked === false) {
state.replaceNewlines = "off";
} else {
state.replaceNewlines = "space";
}
updateUI();
});
const replaceNewLinesButtons = modalEl.querySelectorAll(
".replaceNewLinesButtons .button"
);
for (const button of replaceNewLinesButtons) {
const buttons = modalEl.querySelectorAll(".group[data-id='mode'] button");
for (const button of buttons) {
button.addEventListener("click", (e) => {
state.replaceNewlines = (e.target as HTMLElement).dataset[
"replaceNewLines"
] as "space" | "period";
state.customTextMode = (e.target as HTMLButtonElement).value as
| "simple"
| "repeat"
| "random";
if (state.customTextMode === "simple") {
const text = cleanUpText();
state.customTextLimits.word = `${text.length}`;
state.customTextLimits.time = "";
state.customTextLimits.section = "";
}
updateUI();
});
}
for (const button of modalEl.querySelectorAll(
".group[data-id='fancy'] button"
)) {
button.addEventListener("click", (e) => {
state.removeFancyTypographyEnabled =
(e.target as HTMLButtonElement).value === "true" ? true : false;
updateUI();
});
}
for (const button of modalEl.querySelectorAll(
".group[data-id='control'] button"
)) {
button.addEventListener("click", (e) => {
state.replaceControlCharactersEnabled =
(e.target as HTMLButtonElement).value === "true" ? true : false;
updateUI();
});
}
for (const button of modalEl.querySelectorAll(
".group[data-id='delimiter'] button"
)) {
button.addEventListener("click", (e) => {
state.customTextPipeDelimiter =
(e.target as HTMLButtonElement).value === "true" ? true : false;
if (state.customTextPipeDelimiter && state.customTextLimits.word !== "") {
state.customTextLimits.word = "";
}
if (
!state.customTextPipeDelimiter &&
state.customTextLimits.section !== ""
) {
state.customTextLimits.section = "";
}
handleDelimiterChange();
updateUI();
});
}
for (const button of modalEl.querySelectorAll(
".group[data-id='newlines'] button"
)) {
button.addEventListener("click", (e) => {
state.replaceNewlines = (e.target as HTMLButtonElement).value as
| "off"
| "space"
| "periodSpace";
updateUI();
});
}
modalEl
.querySelector(".group[data-id='limit'] input.words")
?.addEventListener("input", (e) => {
state.customTextLimits.word = (e.target as HTMLInputElement).value;
state.customTextLimits.time = "";
state.customTextLimits.section = "";
updateUI();
});
modalEl
.querySelector(".group[data-id='limit'] input.time")
?.addEventListener("input", (e) => {
state.customTextLimits.time = (e.target as HTMLInputElement).value;
state.customTextLimits.word = "";
state.customTextLimits.section = "";
updateUI();
});
modalEl
.querySelector(".group[data-id='limit'] input.sections")
?.addEventListener("input", (e) => {
state.customTextLimits.section = (e.target as HTMLInputElement).value;
state.customTextLimits.word = "";
state.customTextLimits.time = "";
updateUI();
});
const textarea = modalEl.querySelector("textarea");
textarea?.addEventListener("input", (e) => {
state.textarea = (e.target as HTMLTextAreaElement).value;
@ -390,7 +502,7 @@ async function setup(modalEl: HTMLElement): Promise<void> {
state.textarea = area.value;
});
textarea?.addEventListener("keypress", (e) => {
if (state.longCustomTextWarning) {
if (state.longCustomTextWarning || state.challengeWarning) {
e.preventDefault();
return;
}
@ -408,30 +520,6 @@ async function setup(modalEl: HTMLElement): Promise<void> {
});
}
});
modalEl
.querySelector(".randomInputFields .wordcount input")
?.addEventListener("input", (e) => {
state.randomWordInputs.time = "";
state.randomWordInputs.word = (e.target as HTMLInputElement).value;
state.randomWordInputs.section = "";
updateUI();
});
modalEl
.querySelector(".randomInputFields .time input")
?.addEventListener("input", (e) => {
state.randomWordInputs.time = (e.target as HTMLInputElement).value;
state.randomWordInputs.word = "";
state.randomWordInputs.section = "";
updateUI();
});
modalEl
.querySelector(".randomInputFields .sectioncount input")
?.addEventListener("input", (e) => {
state.randomWordInputs.time = "";
state.randomWordInputs.word = "";
state.randomWordInputs.section = (e.target as HTMLInputElement).value;
updateUI();
});
modalEl.querySelector(".button.apply")?.addEventListener("click", () => {
apply();
});
@ -461,6 +549,10 @@ async function setup(modalEl: HTMLElement): Promise<void> {
state.longCustomTextWarning = false;
updateUI();
});
modalEl.querySelector(".challengeWarning")?.addEventListener("click", () => {
state.challengeWarning = false;
updateUI();
});
}
type IncomingData = {

View file

@ -65,9 +65,9 @@ export function show(): void {
});
}
function hide(): void {
void modal.hide();
}
// function hide(): void {
// void modal.hide();
// }
async function setup(modalEl: HTMLElement): Promise<void> {
const wordsGroupButtons = modalEl.querySelectorAll(".wordsGroup button");

View file

@ -256,7 +256,9 @@ async function apply(set: boolean): Promise<void> {
return;
}
const customText = filteredWords.join(CustomText.delimiter);
const customText = filteredWords.join(
CustomText.getPipeDelimiter() ? "|" : " "
);
hide({
modalChainData: {

View file

@ -16,7 +16,7 @@ function getCheckboxValue(checkbox: string): boolean {
type SharedTestSettings = [
SharedTypes.Config.Mode | null,
SharedTypes.Config.Mode2<SharedTypes.Config.Mode> | null,
SharedTypes.CustomText | null,
SharedTypes.CustomTextData | null,
boolean | null,
boolean | null,
string | null,
@ -49,14 +49,7 @@ function updateURL(): void {
}
if (getCheckboxValue("customText")) {
settings[2] = {
text: CustomText.text,
isWordRandom: CustomText.isWordRandom,
isTimeRandom: CustomText.isTimeRandom,
word: CustomText.word,
time: CustomText.time,
delimiter: CustomText.delimiter,
};
settings[2] = CustomText.getData();
}
if (getCheckboxValue("punctuation")) {

View file

@ -1,7 +1,6 @@
import Config from "../config";
import { capitalizeFirstLetterOfEachWord } from "../utils/strings";
import { cachedFetchJson } from "../utils/json-data";
import * as CustomText from "../test/custom-text";
type BritishEnglishReplacement = {
0: string;
@ -39,14 +38,7 @@ export async function replace(
if (!replacement) return word;
if (
(Config.mode === "quote" ||
(Config.mode === "custom" &&
!CustomText.isTimeRandom &&
!CustomText.isWordRandom &&
!CustomText.isSectionRandom)) &&
replacement[2]?.includes(previousWord)
) {
if (Config.mode === "quote" && replacement[2]?.includes(previousWord)) {
return word;
}

View file

@ -1,4 +1,4 @@
export let text = [
let text: string[] = [
"The",
"quick",
"brown",
@ -9,52 +9,67 @@ export let text = [
"lazy",
"dog",
];
export let isWordRandom = false;
export let isTimeRandom = false;
export let isSectionRandom = false;
export let word = -1;
export let time = -1;
export let section = -1;
export let delimiter = " ";
export function setText(txt: string[]): void {
text = txt;
}
let mode: SharedTypes.CustomTextMode = "repeat";
const limit: SharedTypes.CustomTextLimit = {
value: 9,
mode: "word",
};
let pipeDelimiter = false;
export function getText(): string {
return text.join(" ");
}
export function getTextArray(): string[] {
export function getText(): string[] {
return text;
}
export function setIsWordRandom(val: boolean): void {
isWordRandom = val;
export function setText(txt: string[]): void {
text = txt;
limit.value = text.length;
}
export function setIsTimeRandom(val: boolean): void {
isTimeRandom = val;
export function getMode(): SharedTypes.CustomTextMode {
return mode;
}
export function setIsSectionRandom(val: boolean): void {
isSectionRandom = val;
export function setMode(val: SharedTypes.CustomTextMode): void {
mode = val;
limit.value = text.length;
}
export function setTime(val: number): void {
time = val;
export function getLimit(): SharedTypes.CustomTextLimit {
return limit;
}
export function setWord(val: number): void {
word = val;
export function getLimitValue(): number {
return limit.value;
}
export function setSection(val: number): void {
section = val;
export function getLimitMode(): SharedTypes.CustomTextLimitMode {
return limit.mode;
}
export function setDelimiter(val: string): void {
delimiter = val;
export function setLimitValue(val: number): void {
limit.value = val;
}
export function setLimitMode(val: SharedTypes.CustomTextLimitMode): void {
limit.mode = val;
}
export function getPipeDelimiter(): boolean {
return pipeDelimiter;
}
export function setPipeDelimiter(val: boolean): void {
pipeDelimiter = val;
}
export function getData(): SharedTypes.CustomTextData {
return {
text,
mode,
limit,
pipeDelimiter,
};
}
type CustomTextObject = Record<string, string>;

View file

@ -10,19 +10,11 @@ import { isPopupVisible } from "../utils/misc";
const wrapperId = "practiseWordsPopupWrapper";
type BeforeCustomText = {
text: string[];
isTimeRandom: boolean;
isWordRandom: boolean;
time: number;
word: number;
};
type Before = {
mode: SharedTypes.Config.Mode | null;
punctuation: boolean | null;
numbers: boolean | null;
customText: BeforeCustomText | null;
customText: SharedTypes.CustomTextData | null;
};
export const before: Before = {
@ -106,23 +98,15 @@ export function init(missed: boolean, slow: boolean): boolean {
let customText = null;
if (Config.mode === "custom") {
customText = {
text: CustomText.text,
isWordRandom: CustomText.isWordRandom,
isTimeRandom: CustomText.isTimeRandom,
word: CustomText.word,
time: CustomText.time,
};
customText = CustomText.getData();
}
UpdateConfig.setMode("custom", true);
CustomText.setText(newCustomText);
CustomText.setIsWordRandom(true);
CustomText.setIsTimeRandom(false);
CustomText.setWord(
CustomText.setLimitMode("word");
CustomText.setLimitValue(
(sortableSlowWords.length + sortableMissedWords.length) * 5
);
CustomText.setTime(-1);
setCustomTextName("practise", undefined);

View file

@ -855,7 +855,7 @@ export async function update(
Config.mode,
Config.words,
Config.time,
CustomText,
CustomText.getData(),
CustomTextState.isCustomTextLong() ?? false
);

View file

@ -155,7 +155,7 @@ export function restart(options = {} as RestartOptions): void {
Config.mode,
Config.words,
Config.time,
CustomText,
CustomText.getData(),
CustomTextState.isCustomTextLong() ?? false
)
) {
@ -225,10 +225,11 @@ export function restart(options = {} as RestartOptions): void {
if (PractiseWords.before.customText) {
CustomText.setText(PractiseWords.before.customText.text);
CustomText.setIsTimeRandom(PractiseWords.before.customText.isTimeRandom);
CustomText.setIsWordRandom(PractiseWords.before.customText.isWordRandom);
CustomText.setWord(PractiseWords.before.customText.word);
CustomText.setTime(PractiseWords.before.customText.time);
CustomText.setLimitMode(PractiseWords.before.customText.limit.mode);
CustomText.setLimitValue(PractiseWords.before.customText.limit.value);
CustomText.setPipeDelimiter(
PractiseWords.before.customText.pipeDelimiter
);
}
UpdateConfig.setMode(PractiseWords.before.mode);
@ -537,22 +538,17 @@ export async function addWord(): Promise<void> {
TestWords.words.length >= Config.words &&
Config.words > 0) ||
(Config.mode === "custom" &&
CustomText.isWordRandom &&
TestWords.words.length >= CustomText.word &&
CustomText.word !== 0) ||
(Config.mode === "custom" &&
!CustomText.isWordRandom &&
!CustomText.isTimeRandom &&
!CustomText.isSectionRandom &&
TestWords.words.length >= CustomText.text.length) ||
CustomText.getLimitMode() === "word" &&
TestWords.words.length >= CustomText.getLimitValue() &&
CustomText.getLimitValue() !== 0) ||
(Config.mode === "quote" &&
TestWords.words.length >=
(TestWords.randomQuote?.textSplit?.length ?? 0)) ||
(Config.mode === "custom" &&
CustomText.isSectionRandom &&
WordsGenerator.sectionIndex >= CustomText.section &&
CustomText.getLimitMode() === "section" &&
WordsGenerator.sectionIndex >= CustomText.getLimitValue() &&
WordsGenerator.currentSection.length === 0 &&
CustomText.section !== 0)
CustomText.getLimitValue() !== 0)
) {
return;
}
@ -597,7 +593,7 @@ export async function addWord(): Promise<void> {
: {
//borrow the direction of the current language
...(await JSONData.getCurrentLanguage(Config.language)),
words: CustomText.text,
words: CustomText.getText(),
};
const wordset = await Wordset.withWords(language.words);
@ -748,14 +744,15 @@ function buildCompletedEvent(
const wpmCons = Numbers.roundTo2(Misc.kogasa(stddev3 / avg3));
const wpmConsistency = isNaN(wpmCons) ? 0 : wpmCons;
let customText: SharedTypes.CustomText | null = null;
let customText: SharedTypes.CustomTextDataWithTextLen | null = null;
if (Config.mode === "custom") {
customText = {} as SharedTypes.CustomText;
customText.textLen = CustomText.text.length;
customText.isWordRandom = CustomText.isWordRandom;
customText.isTimeRandom = CustomText.isTimeRandom;
customText.word = CustomText.word;
customText.time = CustomText.time;
const temp = CustomText.getData();
customText = {
textLen: temp.text.length,
mode: temp.mode,
pipeDelimiter: temp.pipeDelimiter,
limit: temp.limit,
};
}
//tags
@ -875,6 +872,8 @@ export async function finish(difficultyFailed = false): Promise<void> {
const ce = buildCompletedEvent(difficultyFailed);
console.debug("Completed event object", ce);
function countUndefined(input: unknown): number {
if (typeof input === "number") {
return isNaN(input) ? 1 : 0;
@ -935,25 +934,12 @@ export async function finish(difficultyFailed = false): Promise<void> {
mode2Number === 0 &&
completedEvent.testDuration < 15) ||
(Config.mode === "custom" &&
!CustomText.isWordRandom &&
!CustomText.isTimeRandom &&
!CustomText.isSectionRandom &&
CustomText.text.length < 10) ||
(CustomText.getLimitMode() === "word" ||
CustomText.getLimitMode() === "section") &&
CustomText.getLimitValue() < 10) ||
(Config.mode === "custom" &&
CustomText.isWordRandom &&
!CustomText.isTimeRandom &&
!CustomText.isSectionRandom &&
CustomText.word < 10) ||
(Config.mode === "custom" &&
!CustomText.isWordRandom &&
!CustomText.isSectionRandom &&
CustomText.isTimeRandom &&
CustomText.time < 15) ||
(Config.mode === "custom" &&
!CustomText.isWordRandom &&
!CustomText.isTimeRandom &&
CustomText.isSectionRandom &&
TestWords.words.length < 10) ||
CustomText.getLimitMode() === "time" &&
CustomText.getLimitValue() < 15) ||
(Config.mode === "zen" && completedEvent.testDuration < 15)
) {
Notifications.add("Test invalid - too short", 0);

View file

@ -47,7 +47,7 @@ function updateTimer(): void {
if (timerDebug) console.time("timer progress update");
if (
Config.mode === "time" ||
(Config.mode === "custom" && CustomText.isTimeRandom)
(Config.mode === "custom" && CustomText.getLimitMode() === "time")
) {
TimerProgress.update();
}
@ -149,14 +149,14 @@ function checkIfTimeIsUp(): void {
if (timerDebug) console.time("times up check");
if (
Config.mode === "time" ||
(Config.mode === "custom" && CustomText.isTimeRandom)
(Config.mode === "custom" && CustomText.getLimitMode() === "time")
) {
if (
(Time.get() >= Config.time &&
Config.time !== 0 &&
Config.mode === "time") ||
(Time.get() >= CustomText.time &&
CustomText.time !== 0 &&
(Time.get() >= CustomText.getLimitValue() &&
CustomText.getLimitValue() !== 0 &&
Config.mode === "custom")
) {
//times up

View file

@ -435,8 +435,8 @@ function updateWordsHeight(force = false): void {
if (
Config.showAllLines &&
Config.mode !== "time" &&
!(CustomText.isWordRandom && CustomText.word === 0) &&
!CustomText.isTimeRandom
CustomText.getLimitMode() !== "time" &&
CustomText.getLimitValue() !== 0
) {
$("#words")
.css("height", "auto")

View file

@ -95,7 +95,7 @@ const miniTimerNumberElement = document.querySelector(
);
function getCurrentCount(): number {
if (Config.mode === "custom" && CustomText.isSectionRandom) {
if (Config.mode === "custom" && CustomText.getLimitMode() === "section") {
return (
(TestWords.words.sectionIndexList[
TestWords.words.currentIndex
@ -110,11 +110,11 @@ export function update(): void {
const time = Time.get();
if (
Config.mode === "time" ||
(Config.mode === "custom" && CustomText.isTimeRandom)
(Config.mode === "custom" && CustomText.getLimitMode() === "time")
) {
let maxtime = Config.time;
if (Config.mode === "custom" && CustomText.isTimeRandom) {
maxtime = CustomText.time;
if (Config.mode === "custom" && CustomText.getLimitMode() === "time") {
maxtime = CustomText.getLimitValue();
}
if (Config.timerStyle === "bar") {
const percent = 100 - ((time + 1) / maxtime) * 100;
@ -154,13 +154,14 @@ export function update(): void {
outof = Config.words;
}
if (Config.mode === "custom") {
if (CustomText.isWordRandom) {
outof = CustomText.word;
} else if (CustomText.isSectionRandom) {
outof = CustomText.section;
} else {
outof = CustomText.text.length;
}
outof = CustomText.getLimitValue();
// if (CustomText.getLimitMode() === "word") {
// outof = CustomText.word;
// } else if (CustomText.isSectionRandom) {
// outof = CustomText.section;
// } else {
// outof = CustomText.text.length;
// }
}
if (Config.mode === "quote") {
outof = TestWords.randomQuote?.textSplit?.length ?? 1;

View file

@ -392,11 +392,7 @@ export function getWordsLimit(): number {
if (Config.showAllLines) {
if (Config.mode === "custom") {
if (CustomText.isWordRandom) {
limit = CustomText.word;
} else if (!CustomText.isTimeRandom && !CustomText.isWordRandom) {
limit = CustomText.text.length;
}
limit = CustomText.getLimitValue();
}
if (Config.mode === "words") {
limit = Config.words;
@ -407,15 +403,17 @@ export function getWordsLimit(): number {
if (Config.mode === "words" && Config.words === 0) {
limit = 100;
}
if (
Config.mode === "custom" &&
CustomText.isWordRandom &&
CustomText.word === 0
) {
limit = 100;
}
if (Config.mode === "custom" && CustomText.delimiter === "|") {
limit = 100;
if (Config.mode === "custom") {
if (
CustomText.getLimitValue() === 0 ||
CustomText.getLimitMode() === "time" ||
CustomText.getLimitMode() === "section"
) {
limit = 100;
} else {
limit =
CustomText.getLimitValue() > 100 ? 100 : CustomText.getLimitValue();
}
}
//funboxes
@ -427,35 +425,6 @@ export function getWordsLimit(): number {
if (Config.mode === "words" && Config.words !== 0 && Config.words < limit) {
limit = Config.words;
}
if (
Config.mode === "custom" &&
!CustomText.isSectionRandom &&
!CustomText.isTimeRandom &&
CustomText.isWordRandom &&
CustomText.word !== 0 &&
CustomText.word < limit
) {
limit = CustomText.word;
}
if (
Config.mode === "custom" &&
!CustomText.isTimeRandom &&
!CustomText.isWordRandom &&
!CustomText.isSectionRandom &&
CustomText.text.length !== 0 &&
CustomText.text.length < limit
) {
let newLimit = 0;
for (const word of CustomText.text) {
if (/ /g.test(word)) {
newLimit += word.split(" ").length;
} else {
newLimit++;
}
}
limit = newLimit;
}
return limit;
}
@ -499,6 +468,7 @@ export async function generateWords(
sectionFunbox?.functions?.pullSection !== undefined;
const limit = getWordsLimit();
console.debug("Words limit", limit);
const wordOrder = getQuoteOrCustomModeWordOrder();
console.debug("Word order", wordOrder);
@ -506,9 +476,9 @@ export async function generateWords(
let wordList = language.words;
if (Config.mode === "custom") {
if (wordOrder === "reverse") {
wordList = CustomText.text.reverse();
wordList = CustomText.getText().reverse();
} else {
wordList = CustomText.text;
wordList = CustomText.getText();
}
}
const wordset = await Wordset.withWords(wordList);
@ -542,20 +512,14 @@ export async function generateWords(
ret.sectionIndexes.push(nextWord.sectionIndex);
const randomSectionStop =
CustomText.isSectionRandom &&
CustomText.section !== 0 &&
sectionIndex >= CustomText.section;
const nonRandomSectionStop =
!CustomText.isSectionRandom &&
!CustomText.isTimeRandom &&
sectionIndex >= wordset.length;
CustomText.getLimitMode() === "section" &&
CustomText.getLimitValue() !== 0 &&
sectionIndex >= CustomText.getLimitValue();
const customModeStop =
Config.mode === "custom" &&
currentSection.length === 0 &&
CustomText.delimiter === "|" &&
(randomSectionStop || nonRandomSectionStop);
randomSectionStop;
if (customModeStop || ret.words.length >= limit) {
stop = true;
@ -744,22 +708,19 @@ export async function getNextWord(
if (Config.mode === "quote") {
randomWord = currentQuote[wordIndex] as string;
} else if (Config.mode === "custom" && CustomText.getMode() === "repeat") {
const customText = CustomText.getText();
randomWord = customText[sectionIndex % customText.length] as string;
} else if (
Config.mode === "custom" &&
!CustomText.isWordRandom &&
!CustomText.isTimeRandom &&
!CustomText.isSectionRandom
) {
randomWord = CustomText.text[sectionIndex] as string;
} else if (
Config.mode === "custom" &&
(CustomText.isWordRandom ||
CustomText.isTimeRandom ||
CustomText.isSectionRandom) &&
CustomText.getMode() === "random" &&
(wordset.length < 4 || PractiseWords.before.mode !== null)
) {
randomWord = wordset.randomWord(funboxFrequency);
} else if (Config.mode === "custom" && CustomText.isSectionRandom) {
} else if (
Config.mode === "custom" &&
CustomText.getLimitMode() === "section"
) {
randomWord = wordset.randomWord(funboxFrequency);
const previousSection = Arrays.nthElementFromArray(sectionHistory, -1);

View file

@ -7,7 +7,8 @@ import * as ConfigEvent from "./observables/config-event";
import { debounce, throttle } from "throttle-debounce";
import * as TestUI from "./test/test-ui";
import { get as getActivePage } from "./states/active-page";
import { isDevEnvironment } from "./utils/misc";
import { canQuickRestart, isDevEnvironment } from "./utils/misc";
import { isCustomTextLong } from "./states/custom-text-name";
let isPreviewingFont = false;
export function previewFontFamily(font: string): void {
@ -56,18 +57,13 @@ window.addEventListener("keydown", function (e) {
window.addEventListener("beforeunload", (event) => {
// Cancel the event as stated by the standard.
if (
(Config.mode === "words" && Config.words < 1000) ||
(Config.mode === "time" && Config.time < 3600) ||
Config.mode === "quote" ||
(Config.mode === "custom" &&
CustomText.isWordRandom &&
CustomText.word < 1000) ||
(Config.mode === "custom" &&
CustomText.isTimeRandom &&
CustomText.time < 1000) ||
(Config.mode === "custom" &&
!CustomText.isWordRandom &&
CustomText.text.length < 1000)
canQuickRestart(
Config.mode,
Config.words,
Config.time,
CustomText.getData(),
isCustomTextLong() ?? false
)
) {
//ignore
} else {

View file

@ -221,29 +221,28 @@ export function canQuickRestart(
mode: string,
words: number,
time: number,
CustomText: SharedTypes.CustomText,
CustomText: SharedTypes.CustomTextData,
customTextIsLong: boolean
): boolean {
const wordsLong = mode === "words" && (words >= 1000 || words === 0);
const timeLong = mode === "time" && (time >= 900 || time === 0);
const customTextLong = mode === "custom" && customTextIsLong;
const customTextRandomWordsLong =
mode === "custom" && CustomText.isWordRandom && CustomText.word >= 1000;
const customTextRandomTimeLong =
mode === "custom" && CustomText.isTimeRandom && CustomText.time > 900;
const customTextNoRandomLong =
mode === "custom" &&
!CustomText.isWordRandom &&
!CustomText.isTimeRandom &&
CustomText.text.length >= 1000;
(CustomText.limit.mode === "word" || CustomText.limit.mode === "section") &&
(CustomText.limit.value >= 1000 || CustomText.limit.value === 0);
const customTextRandomTimeLong =
mode === "custom" &&
CustomText.limit.mode === "time" &&
(CustomText.limit.value >= 900 || CustomText.limit.value === 0);
if (
wordsLong ||
timeLong ||
customTextLong ||
customTextRandomWordsLong ||
customTextRandomTimeLong ||
customTextNoRandomLong
customTextRandomTimeLong
) {
return false;
} else {

View file

@ -110,7 +110,7 @@ export function loadCustomThemeFromUrl(getOverride?: string): void {
type SharedTestSettings = [
SharedTypes.Config.Mode | null,
SharedTypes.Config.Mode2<SharedTypes.Config.Mode> | null,
SharedTypes.CustomText | null,
SharedTypes.CustomTextData | null,
boolean | null,
boolean | null,
string | null,
@ -147,15 +147,10 @@ export function loadTestSettingsFromUrl(getOverride?: string): void {
if (de[2] !== null) {
const customTextSettings = de[2];
CustomText.setText(customTextSettings.text);
CustomText.setIsTimeRandom(customTextSettings.isTimeRandom);
CustomText.setIsWordRandom(customTextSettings.isWordRandom);
if (customTextSettings.isTimeRandom) {
CustomText.setTime(customTextSettings.time);
}
if (customTextSettings.isWordRandom) {
CustomText.setWord(customTextSettings.word);
}
CustomText.setDelimiter(customTextSettings.delimiter);
CustomText.setLimitMode(customTextSettings.limit.mode);
CustomText.setLimitValue(customTextSettings.limit.value);
CustomText.setPipeDelimiter(customTextSettings.pipeDelimiter);
applied["custom text settings"] = "";
}

View file

@ -85,14 +85,14 @@
"display": "True Simp",
"autoRole": true,
"type": "customText",
"parameters": ["miodec",true,10000]
"parameters": ["miodec", "repeat", 10000, "word", false]
}
,{
"name": "bigramSalad",
"display": "Bigram Salad",
"autoRole": true,
"type": "customText",
"parameters": ["to of in it is as at be we he so on an or do if up by my go",true,100],
"parameters": ["to of in it is as at be we he so on an or do if up by my go", "random", 100, "word", false],
"requirements" : {
"wpm": {
"min": 100
@ -104,14 +104,14 @@
"display": "Simp",
"autoRole": true,
"type": "customText",
"parameters": ["miodec",true,1000]
"parameters": ["miodec", "repeat", 1000, "word", false]
}
,{
"name": "antidiseWhat",
"display": "Antidise-what?",
"autoRole": true,
"type": "customText",
"parameters": ["antidisestablishmentarianism",true,1],
"parameters": ["antidisestablishmentarianism","repeat",1,"word",false],
"requirements" : {
"wpm": {
"min": 200
@ -123,14 +123,14 @@
"display": "What's this website called again?",
"autoRole": true,
"type": "customText",
"parameters": ["monkeytype",true,1000]
"parameters": ["monkeytype","repeat",1000, "word", false]
}
,{
"name": "developd",
"display": "Develop'd",
"autoRole": true,
"type": "customText",
"parameters": ["develop",true,1000]
"parameters": ["develop","repeat",1000,"word",false]
}
,{
"name": "slowAndSteady",
@ -153,7 +153,7 @@
"display": "Speed Spacer",
"autoRole": true,
"type": "customText",
"parameters": ["a b c d e f g h i j k l m n o p q r s t u v w x y z",true,100],
"parameters": ["a b c d e f g h i j k l m n o p q r s t u v w x y z","random",100,"word",false],
"requirements" : {
"wpm": {
"min": 100

View file

@ -183,16 +183,6 @@ declare namespace SharedTypes {
punctuation: boolean;
}
interface CustomText {
text: string[];
isWordRandom: boolean;
isTimeRandom: boolean;
word: number;
time: number;
delimiter: string;
textLen?: number;
}
type DBResult<T extends SharedTypes.Config.Mode> = Omit<
SharedTypes.Result<T>,
| "bailedOut"
@ -211,6 +201,7 @@ declare namespace SharedTypes {
| "customText"
| "quoteLength"
| "isPb"
| "customText"
> & {
correctChars?: number; // --------------
incorrectChars?: number; // legacy results
@ -229,7 +220,7 @@ declare namespace SharedTypes {
incompleteTestSeconds?: number;
afkDuration?: number;
tags?: string[];
customText?: CustomText;
customText?: CustomTextDataWithTextLen;
quoteLength?: number;
isPb?: boolean;
};
@ -237,7 +228,7 @@ declare namespace SharedTypes {
interface CompletedEvent extends Result<SharedTypes.Config.Mode> {
keySpacing: number[] | "toolong";
keyDuration: number[] | "toolong";
customText?: CustomText;
customText?: CustomTextDataWithTextLen;
wpmConsistency: number;
challenge?: string | null;
keyOverlap: number;
@ -248,6 +239,24 @@ declare namespace SharedTypes {
hash?: string;
}
type CustomTextMode = "repeat" | "random";
type CustomTextLimitMode = "word" | "time" | "section";
type CustomTextLimit = {
value: number;
mode: CustomTextLimitMode;
};
type CustomTextData = {
text: string[];
mode: CustomTextMode;
limit: CustomTextLimit;
pipeDelimiter: boolean;
};
type CustomTextDataWithTextLen = Omit<CustomTextData, "text"> & {
textLen: number;
};
interface ResultFilters {
_id: string;
name: string;