Skip to content

Commit da67133

Browse files
fehmerMiodec
andauthored
feat(funbox): add polyglot (@fehmer) (monkeytypegame#6454)
Add polyglot funbox which let you practice on multiple languages at once in a single test. --------- Co-authored-by: Miodec <[email protected]>
1 parent a8ce609 commit da67133

File tree

14 files changed

+229
-22
lines changed

14 files changed

+229
-22
lines changed

frontend/src/html/pages/settings.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,19 @@
351351
</div>
352352
</div>
353353
<div class="sectionSpacer"></div>
354+
<div class="section" data-config-name="customPolyglot">
355+
<div class="groupTitle">
356+
<i class="fas fa-language"></i>
357+
<span>polyglot languages</span>
358+
</div>
359+
<div class="text">
360+
Select which languages you want the polyglot funbox to use.
361+
</div>
362+
<div class="inputs">
363+
<select multiple></select>
364+
</div>
365+
</div>
366+
<div class="sectionSpacer"></div>
354367
</div>
355368
<button id="group_input" class="text sectionGroupTitle" group="input">
356369
<i class="fas fa-chevron-down"></i>

frontend/src/ts/commandline/commandline.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function addCommandlineBackground(): void {
5353

5454
type ShowSettings = {
5555
subgroupOverride?: CommandsSubgroup | string;
56+
commandOverride?: string;
5657
singleListOverride?: boolean;
5758
};
5859

@@ -102,6 +103,25 @@ export function show(
102103
subgroupOverride = null;
103104
usingSingleList = Config.singleListCommandLine === "on";
104105
}
106+
107+
let showInputCommand: Command | undefined = undefined;
108+
109+
if (settings?.commandOverride !== undefined) {
110+
const command = (await getList()).filter(
111+
(c) => c.id === settings.commandOverride
112+
)[0];
113+
if (command === undefined) {
114+
Notifications.add(`Command ${settings.commandOverride} not found`, 0);
115+
} else if (command?.input !== true) {
116+
Notifications.add(
117+
`Command ${settings.commandOverride} is not an input command`,
118+
0
119+
);
120+
} else {
121+
showInputCommand = command;
122+
}
123+
}
124+
105125
if (settings?.singleListOverride) {
106126
usingSingleList = settings.singleListOverride;
107127
}
@@ -114,6 +134,20 @@ export function show(
114134
await updateActiveCommand();
115135
setTimeout(() => {
116136
keepActiveCommandInView();
137+
if (showInputCommand) {
138+
const escaped =
139+
showInputCommand.display.split("</i>")[1] ??
140+
showInputCommand.display;
141+
mode = "input";
142+
inputModeParams = {
143+
command: showInputCommand,
144+
placeholder: escaped,
145+
value: showInputCommand.defaultValue?.() ?? "",
146+
icon: showInputCommand.icon ?? "fa-chevron-right",
147+
};
148+
updateInput(inputModeParams.value as string);
149+
hideCommands();
150+
}
117151
}, 1);
118152
},
119153
});

frontend/src/ts/commandline/lists.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,19 @@ export const commands: CommandsSubgroup = {
226226
UpdateConfig.setCustomLayoutfluid(input);
227227
},
228228
},
229+
{
230+
id: "changeCustomPolyglot",
231+
display: "Polyglot languages...",
232+
defaultValue: (): string => {
233+
return Config.customPolyglot.join(" ");
234+
},
235+
input: true,
236+
icon: "fa-language",
237+
exec: ({ input }): void => {
238+
if (input === undefined) return;
239+
void UpdateConfig.setCustomPolyglot(input.split(" "));
240+
},
241+
},
229242

230243
//input
231244
...FreedomModeCommands,

frontend/src/ts/config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,6 +1906,26 @@ export function setCustomLayoutfluid(
19061906
return true;
19071907
}
19081908

1909+
export function setCustomPolyglot(
1910+
value: ConfigSchemas.CustomPolyglot,
1911+
nosave?: boolean
1912+
): boolean {
1913+
if (
1914+
!isConfigValueValid(
1915+
"customPolyglot",
1916+
value,
1917+
ConfigSchemas.CustomPolyglotSchema
1918+
)
1919+
)
1920+
return false;
1921+
1922+
config.customPolyglot = value;
1923+
saveToLocalStorage("customPolyglot", nosave);
1924+
ConfigEvent.dispatch("customPolyglot", config.customPolyglot);
1925+
1926+
return true;
1927+
}
1928+
19091929
export function setCustomBackgroundSize(
19101930
value: ConfigSchemas.CustomBackgroundSize,
19111931
nosave?: boolean
@@ -2012,6 +2032,7 @@ export async function apply(
20122032
true
20132033
);
20142034
setCustomLayoutfluid(configObj.customLayoutfluid, true);
2035+
setCustomPolyglot(configObj.customPolyglot, true);
20152036
setCustomBackground(configObj.customBackground, true);
20162037
setCustomBackgroundSize(configObj.customBackgroundSize, true);
20172038
setCustomBackgroundFilter(configObj.customBackgroundFilter, true);

frontend/src/ts/constants/default-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const obj = {
9393
customBackgroundSize: "cover",
9494
customBackgroundFilter: [0, 1, 1, 1],
9595
customLayoutfluid: "qwerty#dvorak#colemak",
96+
customPolyglot: ["english", "spanish", "french", "german"],
9697
monkeyPowerLevel: "off",
9798
minBurst: "off",
9899
minBurstCustomSpeed: 100,

frontend/src/ts/elements/modes-notice.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { isAuthenticated } from "../firebase";
99
import * as CustomTextState from "../states/custom-text-name";
1010
import { getLanguageDisplayString } from "../utils/strings";
1111
import Format from "../utils/format";
12+
import { getActiveFunboxNames } from "../test/funbox/list";
1213

1314
ConfigEvent.subscribe((eventKey) => {
1415
if (
@@ -25,6 +26,7 @@ ConfigEvent.subscribe((eventKey) => {
2526
"showAverage",
2627
"typingSpeedUnit",
2728
"quickRestart",
29+
"changeCustomPolyglot",
2830
].includes(eventKey)
2931
) {
3032
void update();
@@ -91,7 +93,9 @@ export async function update(): Promise<void> {
9193
);
9294
}
9395

94-
if (Config.mode !== "zen") {
96+
const usingPolyglot = getActiveFunboxNames().includes("polyglot");
97+
98+
if (Config.mode !== "zen" && !usingPolyglot) {
9599
$(".pageTest #testModesNotice").append(
96100
`<button class="textButton" commands="languages"><i class="fas fa-globe-americas"></i>${getLanguageDisplayString(
97101
Config.language,
@@ -100,6 +104,19 @@ export async function update(): Promise<void> {
100104
);
101105
}
102106

107+
if (usingPolyglot) {
108+
const languages = Config.customPolyglot
109+
.map((lang) => {
110+
const langDisplay = getLanguageDisplayString(lang, true);
111+
return langDisplay;
112+
})
113+
.join(", ");
114+
115+
$(".pageTest #testModesNotice").append(
116+
`<button class="textButton" commandId="changeCustomPolyglot"><i class="fas fa-globe-americas"></i>${languages}</button>`
117+
);
118+
}
119+
103120
if (Config.difficulty === "expert") {
104121
$(".pageTest #testModesNotice").append(
105122
`<button class="textButton" commands="difficulty"><i class="fas fa-star-half-alt"></i>expert</button>`

frontend/src/ts/event-handlers/test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ $(".pageTest").on("click", "#testModesNotice .textButton", async (event) => {
2121
(await getCommandline()).show({ subgroupOverride: attr });
2222
});
2323

24+
$(".pageTest").on("click", "#testModesNotice .textButton", async (event) => {
25+
const attr = $(event.currentTarget).attr("commandId");
26+
if (attr === undefined) return;
27+
(await getCommandline()).show({ commandOverride: attr });
28+
});
29+
2430
$(".pageTest").on("click", "#testConfig .wordCount .textButton", (e) => {
2531
const wrd = $(e.currentTarget).attr("wordCount");
2632
if (wrd === "custom") {

frontend/src/ts/pages/settings.ts

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ import {
3232
import { getActiveFunboxNames } from "../test/funbox/list";
3333
import { SnapshotPreset } from "../constants/default-snapshot";
3434
import { LayoutsList } from "../constants/layouts";
35-
import { DataArrayPartial } from "slim-select/store";
35+
import { DataArrayPartial, Optgroup } from "slim-select/store";
3636

3737
type SettingsGroups<T extends ConfigValue> = Record<string, SettingsGroup<T>>;
3838

3939
let customLayoutFluidSelect: SlimSelect | undefined;
40+
let customPolyglotSelect: SlimSelect | undefined;
4041

4142
export const groups: SettingsGroups<ConfigValue> = {};
4243

@@ -476,21 +477,12 @@ async function fillSettingsPage(): Promise<void> {
476477
".pageSettings .section[data-config-name='language'] select"
477478
) as Element;
478479

479-
let html = "";
480-
if (languageGroups) {
481-
for (const group of languageGroups) {
482-
html += `<optgroup label="${group.name}">`;
483-
for (const language of group.languages) {
484-
const selected = language === Config.language ? "selected" : "";
485-
const text = Strings.getLanguageDisplayString(language);
486-
html += `<option value="${language}" ${selected}>${text}</option>`;
487-
}
488-
html += `</optgroup>`;
489-
}
490-
}
491-
element.innerHTML = html;
492480
new SlimSelect({
493481
select: element,
482+
data: getLanguageDropdownData(
483+
languageGroups ?? [],
484+
(language) => language === Config.language
485+
),
494486
settings: {
495487
searchPlaceholder: "search",
496488
},
@@ -687,11 +679,11 @@ async function fillSettingsPage(): Promise<void> {
687679
Config.keymapSize
688680
);
689681

690-
const customLayoutfluidElement = document.querySelector(
691-
".pageSettings .section[data-config-name='customLayoutfluid'] select"
692-
) as Element;
693-
694682
if (customLayoutFluidSelect === undefined) {
683+
const customLayoutfluidElement = document.querySelector(
684+
".pageSettings .section[data-config-name='customLayoutfluid'] select"
685+
) as Element;
686+
695687
customLayoutFluidSelect = new SlimSelect({
696688
select: customLayoutfluidElement,
697689
settings: { keepOrder: true },
@@ -706,6 +698,27 @@ async function fillSettingsPage(): Promise<void> {
706698
});
707699
}
708700

701+
if (customPolyglotSelect === undefined) {
702+
const customPolyglotElement = document.querySelector(
703+
".pageSettings .section[data-config-name='customPolyglot'] select"
704+
) as Element;
705+
706+
customPolyglotSelect = new SlimSelect({
707+
select: customPolyglotElement,
708+
data: getLanguageDropdownData(languageGroups ?? [], (language) =>
709+
Config.customPolyglot.includes(language)
710+
),
711+
events: {
712+
afterChange: (newVal): void => {
713+
const customPolyglot = newVal.map((it) => it.value);
714+
if (customPolyglot.toSorted() !== Config.customPolyglot.toSorted()) {
715+
void UpdateConfig.setCustomPolyglot(customPolyglot);
716+
}
717+
},
718+
},
719+
});
720+
}
721+
709722
$(".pageSettings .section[data-config-name='tapeMargin'] input").val(
710723
Config.tapeMargin
711724
);
@@ -911,6 +924,13 @@ export async function update(groupUpdate = true): Promise<void> {
911924
) {
912925
customLayoutFluidSelect.setData(getLayoutfluidDropdownData());
913926
}
927+
928+
if (
929+
customPolyglotSelect !== undefined &&
930+
customPolyglotSelect.getSelected() !== Config.customPolyglot
931+
) {
932+
customPolyglotSelect.setSelected(Config.customPolyglot);
933+
}
914934
}
915935
function toggleSettingsGroup(groupName: string): void {
916936
const groupEl = $(`.pageSettings .settingsGroup.${groupName}`);
@@ -1359,6 +1379,23 @@ export function setEventDisabled(value: boolean): void {
13591379
configEventDisabled = value;
13601380
}
13611381

1382+
function getLanguageDropdownData(
1383+
languageGroups: JSONData.LanguageGroup[],
1384+
isActive: (val: string) => boolean
1385+
): DataArrayPartial {
1386+
return languageGroups.map(
1387+
(group) =>
1388+
({
1389+
label: group.name,
1390+
options: group.languages.map((language) => ({
1391+
text: Strings.getLanguageDisplayString(language),
1392+
value: language,
1393+
selected: isActive(language),
1394+
})),
1395+
} as Optgroup)
1396+
);
1397+
}
1398+
13621399
function getLayoutfluidDropdownData(): DataArrayPartial {
13631400
const customLayoutfluidActive = Config.customLayoutfluid.split("#");
13641401
return [

frontend/src/ts/test/funbox/funbox-functions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,16 @@ const list: Partial<Record<FunboxName, FunboxFunctions>> = {
645645
return word.toUpperCase();
646646
},
647647
},
648+
polyglot: {
649+
async withWords(_words) {
650+
const promises = Config.customPolyglot.map(JSONData.getLanguage);
651+
652+
const languages = await Promise.all(promises);
653+
const wordSet = languages.flatMap((it) => it.words);
654+
Arrays.shuffle(wordSet);
655+
return new Wordset(wordSet);
656+
},
657+
},
648658
};
649659

650660
export function getFunboxFunctions(): Record<FunboxName, FunboxFunctions> {

packages/contracts/src/schemas/configs.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ export type CustomBackgroundFilter = z.infer<
198198
export const CustomLayoutFluidSchema = z.string().regex(/^[0-9a-zA-Z_#]+$/); //TODO better regex
199199
export type CustomLayoutFluid = z.infer<typeof CustomLayoutFluidSchema>;
200200

201+
export const CustomPolyglotSchema = z.array(LanguageSchema).min(1);
202+
export type CustomPolyglot = z.infer<typeof CustomPolyglotSchema>;
203+
201204
export const MonkeyPowerLevelSchema = z.enum(["off", "1", "2", "3", "4"]);
202205
export type MonkeyPowerLevel = z.infer<typeof MonkeyPowerLevelSchema>;
203206

@@ -383,6 +386,7 @@ export const ConfigSchema = z
383386
lazyMode: z.boolean(),
384387
showAverage: ShowAverageSchema,
385388
maxLineWidth: MaxLineWidthSchema,
389+
customPolyglot: CustomPolyglotSchema,
386390
} satisfies Record<string, ZodSchema>)
387391
.strict();
388392

@@ -496,6 +500,7 @@ export const ConfigGroupsLiteral = {
496500
lazyMode: "input",
497501
showAverage: "hideElements",
498502
maxLineWidth: "appearance",
503+
customPolyglot: "behavior",
499504
} as const satisfies Record<ConfigKey, ConfigGroupName>;
500505

501506
export type ConfigGroups = typeof ConfigGroupsLiteral;

0 commit comments

Comments
 (0)