Skip to content

Commit 5ab7bfb

Browse files
authored
refactor: move themes list to typescript (@fehmer) (monkeytypegame#6489)
Co-authored-by: fehmer <[email protected]>
1 parent dc6d451 commit 5ab7bfb

File tree

19 files changed

+1544
-1614
lines changed

19 files changed

+1544
-1614
lines changed

docs/THEMES.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,29 @@ Then add this code to your file:
3636
Here is an image showing what all the properties correspond to:
3737
<img width="1552" alt="Screenshot showing the page elements controlled by each color property" src="https://user-images.githubusercontent.com/83455454/149196967-abb69795-0d38-466b-a867-5aaa46452976.png">
3838

39-
Change the corresponding hex codes to create your theme. Then, go to `./frontend/static/themes/_list.json` and add the following code to the very end of the file (inside the square brackets):
40-
41-
```json
42-
{
43-
"name": "theme_name",
44-
"bgColor": "#ffffff",
45-
"mainColor": "#ffffff",
46-
"subColor": "#ffffff",
47-
"textColor": "#ffffff"
39+
Change the corresponding hex codes to create your theme.
40+
Then, go to `./packages/contracts/src/schemas/themes.ts` and add your new theme name at the _end_ of the `ThemeNameSchema` enum. Make sure to end the line with a comma.
41+
42+
```typescript
43+
export const ThemeNameSchema = z.enum([
44+
"8008",
45+
"80s_after_dark",
46+
...
47+
"your_theme_name",
48+
```
49+
50+
Then, go to `./frontend/src/ts/constants/themes.ts` and add the following code to the _end_ of the `themes` object near to the very end of the file:
51+
52+
```typescript
53+
export const themes: Record<ThemeName, Omit<Theme, "name">> = {
54+
...
55+
your_theme_name: {
56+
bgColor "#ffffff",
57+
mainColor "#ffffff",
58+
subColor "#ffffff",
59+
textColor "#ffffff"
4860
},
61+
}
4962
```
5063
5164
Make sure the name you put matches the name of the file you created (without the `.css` file extension). Add the text color and background color of your theme to their respective fields.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { readdirSync } from "fs";
2+
import { ThemesList } from "../../src/ts/constants/themes";
3+
4+
describe("themes", () => {
5+
it("should not have duplicates", () => {
6+
const duplicates = ThemesList.filter(
7+
(item, index) => ThemesList.indexOf(item) !== index
8+
);
9+
expect(duplicates).toEqual([]);
10+
});
11+
it("should have all related css files", () => {
12+
const themeFiles = listThemeFiles();
13+
14+
const missingThemeFiles = ThemesList.filter(
15+
(it) => !themeFiles.includes(it.name)
16+
).map((it) => `fontend/static/themes/${it}.css`);
17+
18+
expect(missingThemeFiles, "missing theme css files").toEqual([]);
19+
});
20+
it("should not have additional css files", () => {
21+
const themeFiles = listThemeFiles();
22+
23+
const additionalThemeFiles = themeFiles
24+
.filter((it) => !ThemesList.some((theme) => theme.name === it))
25+
.map((it) => `fontend/static/themes/${it}.css`);
26+
27+
expect(
28+
additionalThemeFiles,
29+
"additional theme css files not declared in frontend/src/ts/constants/themes.ts"
30+
).toEqual([]);
31+
});
32+
});
33+
34+
function listThemeFiles() {
35+
return readdirSync(import.meta.dirname + "/../../static/themes").map((it) =>
36+
it.substring(0, it.length - 4)
37+
);
38+
}

frontend/__tests__/root/config.spec.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -328,11 +328,9 @@ describe("Config", () => {
328328
});
329329
it("setFavThemes", () => {
330330
expect(Config.setFavThemes([])).toBe(true);
331-
expect(Config.setFavThemes(["test"])).toBe(true);
332-
expect(Config.setFavThemes([stringOfLength(50)])).toBe(true);
333-
331+
expect(Config.setFavThemes(["8008", "80s_after_dark"])).toBe(true);
332+
expect(Config.setFavThemes(["test"] as any)).toBe(false);
334333
expect(Config.setFavThemes("invalid" as any)).toBe(false);
335-
expect(Config.setFavThemes([stringOfLength(51)])).toBe(false);
336334
});
337335
it("setFunbox", () => {
338336
expect(Config.setFunbox(["mirror"])).toBe(true);
@@ -407,29 +405,20 @@ describe("Config", () => {
407405
it("setTheme", () => {
408406
expect(Config.setTheme("serika")).toBe(true);
409407
expect(Config.setTheme("serika_dark")).toBe(true);
410-
expect(Config.setTheme(stringOfLength(50))).toBe(true);
411408

412-
expect(Config.setTheme("serika dark")).toBe(false);
413-
expect(Config.setTheme("serika-dark")).toBe(false);
414-
expect(Config.setTheme(stringOfLength(51))).toBe(false);
409+
expect(Config.setTheme("invalid" as any)).toBe(false);
415410
});
416411
it("setThemeLight", () => {
417412
expect(Config.setThemeLight("serika")).toBe(true);
418413
expect(Config.setThemeLight("serika_dark")).toBe(true);
419-
expect(Config.setThemeLight(stringOfLength(50))).toBe(true);
420414

421-
expect(Config.setThemeLight("serika dark")).toBe(false);
422-
expect(Config.setThemeLight("serika-dark")).toBe(false);
423-
expect(Config.setThemeLight(stringOfLength(51))).toBe(false);
415+
expect(Config.setThemeLight("invalid" as any)).toBe(false);
424416
});
425417
it("setThemeDark", () => {
426418
expect(Config.setThemeDark("serika")).toBe(true);
427419
expect(Config.setThemeDark("serika_dark")).toBe(true);
428-
expect(Config.setThemeDark(stringOfLength(50))).toBe(true);
429420

430-
expect(Config.setThemeDark("serika dark")).toBe(false);
431-
expect(Config.setThemeDark("serika-dark")).toBe(false);
432-
expect(Config.setThemeDark(stringOfLength(51))).toBe(false);
421+
expect(Config.setThemeDark("invalid" as any)).toBe(false);
433422
});
434423
it("setLanguage", () => {
435424
expect(Config.setLanguage("english")).toBe(true);

frontend/scripts/json-validation.cjs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -47,46 +47,6 @@ function validateOthers() {
4747
return reject(new Error(fontsValidator.errors[0].message));
4848
}
4949

50-
//themes
51-
const themesData = JSON.parse(
52-
fs.readFileSync("./static/themes/_list.json", {
53-
encoding: "utf8",
54-
flag: "r",
55-
})
56-
);
57-
const themesSchema = {
58-
type: "array",
59-
items: {
60-
type: "object",
61-
properties: {
62-
name: { type: "string" },
63-
bgColor: { type: "string" },
64-
mainColor: { type: "string" },
65-
},
66-
required: ["name", "bgColor", "mainColor"],
67-
},
68-
};
69-
const themesValidator = ajv.compile(themesSchema);
70-
if (themesValidator(themesData)) {
71-
console.log("Themes list JSON schema is \u001b[32mvalid\u001b[0m");
72-
} else {
73-
console.log("Themes list JSON schema is \u001b[31minvalid\u001b[0m");
74-
return reject(new Error(themesValidator.errors[0].message));
75-
}
76-
//check if files exist
77-
for (const theme of themesData) {
78-
const themeName = theme.name;
79-
const fileName = `${themeName}.css`;
80-
const themePath = `./static/themes/${fileName}`;
81-
if (!fs.existsSync(themePath)) {
82-
console.log(`File ${fileName} was \u001b[31mnot found\u001b[0m`);
83-
// return reject(new Error(`File for theme ${themeName} does not exist`));
84-
return reject(
85-
`Could not find file ${fileName} for theme ${themeName} - make sure the file exists and is named correctly`
86-
);
87-
}
88-
}
89-
9050
//challenges
9151
const challengesSchema = {
9252
type: "array",

frontend/src/ts/commandline/lists.ts

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ import CustomThemesListCommands from "./lists/custom-themes-list";
7777
import PresetsCommands from "./lists/presets";
7878
import LayoutsCommands from "./lists/layouts";
7979
import FunboxCommands from "./lists/funbox";
80-
import ThemesCommands, { update as updateThemesCommands } from "./lists/themes";
80+
import ThemesCommands from "./lists/themes";
8181
import LoadChallengeCommands, {
8282
update as updateLoadChallengeCommands,
8383
} from "./lists/load-challenge";
@@ -130,17 +130,6 @@ fontsPromise
130130
);
131131
});
132132

133-
const themesPromise = JSONData.getThemesList();
134-
themesPromise
135-
.then((themes) => {
136-
updateThemesCommands(themes);
137-
})
138-
.catch((e: unknown) => {
139-
console.error(
140-
Misc.createErrorMessage(e, "Failed to update themes commands")
141-
);
142-
});
143-
144133
const challengesPromise = JSONData.getChallengeList();
145134
challengesPromise
146135
.then((challenges) => {
@@ -501,12 +490,7 @@ export function doesListExist(listName: string): boolean {
501490
export async function getList(
502491
listName: ListsObjectKeys
503492
): Promise<CommandsSubgroup> {
504-
await Promise.allSettled([
505-
languagesPromise,
506-
fontsPromise,
507-
themesPromise,
508-
challengesPromise,
509-
]);
493+
await Promise.allSettled([languagesPromise, fontsPromise, challengesPromise]);
510494
const list = lists[listName];
511495
if (!list) {
512496
Notifications.add(`List not found: ${listName}`, -1);
@@ -547,12 +531,7 @@ export function getTopOfStack(): CommandsSubgroup {
547531

548532
let singleList: CommandsSubgroup | undefined;
549533
export async function getSingleSubgroup(): Promise<CommandsSubgroup> {
550-
await Promise.allSettled([
551-
languagesPromise,
552-
fontsPromise,
553-
themesPromise,
554-
challengesPromise,
555-
]);
534+
await Promise.allSettled([languagesPromise, fontsPromise, challengesPromise]);
556535

557536
const singleCommands: Command[] = [];
558537
for (const command of commands.list) {

frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ThemeName } from "@monkeytype/contracts/schemas/configs";
12
import Config, * as UpdateConfig from "../../config";
23
import { randomTheme } from "../../controllers/theme-controller";
34
import { Command } from "../types";
@@ -10,14 +11,14 @@ const commands: Command[] = [
1011
available: (): boolean => {
1112
return (
1213
!Config.customTheme &&
13-
!Config.favThemes.includes(randomTheme ?? Config.theme)
14+
!Config.favThemes.includes((randomTheme as ThemeName) ?? Config.theme)
1415
);
1516
},
1617
exec: (): void => {
1718
const { theme, favThemes, customTheme } = Config;
1819
const themeToUpdate = randomTheme ?? theme;
19-
if (!customTheme && !favThemes.includes(themeToUpdate)) {
20-
UpdateConfig.setFavThemes([...favThemes, themeToUpdate]);
20+
if (!customTheme && !favThemes.includes(themeToUpdate as ThemeName)) {
21+
UpdateConfig.setFavThemes([...favThemes, themeToUpdate as ThemeName]);
2122
}
2223
},
2324
},
@@ -28,12 +29,12 @@ const commands: Command[] = [
2829
available: (): boolean => {
2930
return (
3031
!Config.customTheme &&
31-
Config.favThemes.includes(randomTheme ?? Config.theme)
32+
Config.favThemes.includes((randomTheme as ThemeName) ?? Config.theme)
3233
);
3334
},
3435
exec: (): void => {
3536
const { theme, favThemes, customTheme } = Config;
36-
const themeToUpdate = randomTheme ?? theme;
37+
const themeToUpdate = (randomTheme as ThemeName) ?? theme;
3738
if (!customTheme && favThemes.includes(themeToUpdate)) {
3839
UpdateConfig.setFavThemes([
3940
...favThemes.filter((t) => t !== themeToUpdate),

frontend/src/ts/commandline/lists/themes.ts

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,37 @@ import Config, * as UpdateConfig from "../../config";
22
import { capitalizeFirstLetterOfEachWord } from "../../utils/strings";
33
import * as ThemeController from "../../controllers/theme-controller";
44
import { Command, CommandsSubgroup } from "../types";
5-
import { Theme } from "../../utils/json-data";
5+
import { Theme, ThemesList } from "../../constants/themes";
6+
import { not } from "@monkeytype/util/predicates";
7+
8+
const isFavorite = (theme: Theme): boolean =>
9+
Config.favThemes.includes(theme.name);
610

711
const subgroup: CommandsSubgroup = {
812
title: "Theme...",
913
configKey: "theme",
10-
list: [],
14+
list: [
15+
...ThemesList.filter(isFavorite),
16+
...ThemesList.filter(not(isFavorite)),
17+
].map((theme: Theme) => ({
18+
id: "changeTheme" + capitalizeFirstLetterOfEachWord(theme.name),
19+
display: theme.name.replace(/_/g, " "),
20+
configValue: theme.name,
21+
// customStyle: `color:${theme.mainColor};background:${theme.bgColor};`,
22+
customData: {
23+
mainColor: theme.mainColor,
24+
bgColor: theme.bgColor,
25+
subColor: theme.subColor,
26+
textColor: theme.textColor,
27+
},
28+
hover: (): void => {
29+
// previewTheme(theme.name);
30+
ThemeController.preview(theme.name);
31+
},
32+
exec: (): void => {
33+
UpdateConfig.setTheme(theme.name);
34+
},
35+
})),
1136
};
1237

1338
const commands: Command[] = [
@@ -19,54 +44,4 @@ const commands: Command[] = [
1944
},
2045
];
2146

22-
function update(themes: Theme[]): void {
23-
subgroup.list = [];
24-
const favs: Command[] = [];
25-
themes.forEach((theme) => {
26-
if (Config.favThemes.includes(theme.name)) {
27-
favs.push({
28-
id: "changeTheme" + capitalizeFirstLetterOfEachWord(theme.name),
29-
display: theme.name.replace(/_/g, " "),
30-
configValue: theme.name,
31-
// customStyle: `color:${theme.mainColor};background:${theme.bgColor};`,
32-
customData: {
33-
mainColor: theme.mainColor,
34-
bgColor: theme.bgColor,
35-
subColor: theme.subColor,
36-
textColor: theme.textColor,
37-
},
38-
hover: (): void => {
39-
// previewTheme(theme.name);
40-
ThemeController.preview(theme.name);
41-
},
42-
exec: (): void => {
43-
UpdateConfig.setTheme(theme.name);
44-
},
45-
});
46-
} else {
47-
subgroup.list.push({
48-
id: "changeTheme" + capitalizeFirstLetterOfEachWord(theme.name),
49-
display: theme.name.replace(/_/g, " "),
50-
configValue: theme.name,
51-
// customStyle: `color:${theme.mainColor};background:${theme.bgColor}`,
52-
customData: {
53-
mainColor: theme.mainColor,
54-
bgColor: theme.bgColor,
55-
subColor: theme.subColor,
56-
textColor: theme.textColor,
57-
},
58-
hover: (): void => {
59-
// previewTheme(theme.name);
60-
ThemeController.preview(theme.name);
61-
},
62-
exec: (): void => {
63-
UpdateConfig.setTheme(theme.name);
64-
},
65-
});
66-
}
67-
});
68-
subgroup.list = [...favs, ...subgroup.list];
69-
}
70-
7147
export default commands;
72-
export { update };

0 commit comments

Comments
 (0)