Add memoize async util (rewrite get json lists in misc) (#3596) Bruception Miodec

* Add memoize async util

* Stricter types

* type cast

* added getjson function

* removed unnecessary code
removed comments

* ignoring cache if value is undefined

* checking if layouts list exists

* messed up merge

* checking if layouts exist

* throwing if layouts list or layout is undefined

* catching in case layouts or layout is undefined

* catching in case layouts list or layout is undefined

* breaking in case layouts list is undefined

* showing a message if layout list is undefined

* removed console log

* themes list can return undefined

* removed catch from fetch json

* getlayoutslist no longer can return undefined

* console logging then throwing again

* rethrowing with a different message

* try catch

* catch

* try catching

* try catching

* typo

* url fix

* moved definition up

* moved functions up

* moved functions up

* removed unnecessary update

* catching errors

* updated return type
not returning

* try catching

* updated return type
throwing if list ends up undefined

* removed return

* moved functions up

* try catching

* checking if not undefined

* removed unused file

* try catching

* try catching

* rewrote language getting

* rewrote funbox

* try catching

* notification instead of console error

* notification instead of console error

* rewrote fonts

* rewrote challenges

* try catch

* typo

* rewrote contributors and supporters

Co-authored-by: Miodec <jack@monkeytype.com>
This commit is contained in:
Bruce Berrios 2022-10-30 17:53:24 -04:00 committed by GitHub
parent cbdfb732a1
commit 1eb71dc517
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 710 additions and 418 deletions

View file

@ -676,34 +676,46 @@ $(".pageAccount .topFilters .button.toggleAdvancedFilters").on("click", () => {
});
export async function appendButtons(): Promise<void> {
await Misc.getLanguageList().then((languages) => {
languages.forEach((language) => {
$(
".pageAccount .content .filterButtons .buttonsAndTitle.languages .buttons"
).append(
`<div class="button" filter="${language}">${language.replace(
"_",
" "
)}</div>`
await Misc.getLanguageList()
.then((languages) => {
languages.forEach((language) => {
$(
".pageAccount .content .filterButtons .buttonsAndTitle.languages .buttons"
).append(
`<div class="button" filter="${language}">${language.replace(
"_",
" "
)}</div>`
);
});
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to append language buttons")
);
});
});
$(
".pageAccount .content .filterButtons .buttonsAndTitle.funbox .buttons"
).append(`<div class="button" filter="none">none</div>`);
await Misc.getFunboxList().then((funboxModes) => {
funboxModes.forEach((funbox) => {
$(
".pageAccount .content .filterButtons .buttonsAndTitle.funbox .buttons"
).append(
`<div class="button" filter="${funbox.name}">${funbox.name.replace(
/_/g,
" "
)}</div>`
await Misc.getFunboxList()
.then((funboxModes) => {
funboxModes.forEach((funbox) => {
$(
".pageAccount .content .filterButtons .buttonsAndTitle.funbox .buttons"
).append(
`<div class="button" filter="${funbox.name}">${funbox.name.replace(
/_/g,
" "
)}</div>`
);
});
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to append funbox buttons")
);
});
});
}
export function removeButtons(): void {

View file

@ -98,30 +98,66 @@ import * as Notifications from "../elements/notifications";
import * as VideoAdPopup from "../popups/video-ad-popup";
import * as ShareTestSettingsPopup from "../popups/share-test-settings-popup";
Misc.getLayoutsList().then((layouts) => {
updateLayoutsCommands(layouts);
updateKeymapLayoutsCommands(layouts);
});
Misc.getLayoutsList()
.then((layouts) => {
updateLayoutsCommands(layouts);
updateKeymapLayoutsCommands(layouts);
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to update layouts commands")
);
});
Misc.getLanguageList().then((languages) => {
updateLanguagesCommands(languages);
});
Misc.getLanguageList()
.then((languages) => {
updateLanguagesCommands(languages);
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to update language commands")
);
});
Misc.getFunboxList().then((funboxes) => {
updateFunboxCommands(funboxes);
});
Misc.getFunboxList()
.then((funboxes) => {
updateFunboxCommands(funboxes);
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to update funbox commands")
);
});
Misc.getFontsList().then((fonts) => {
updateFontFamilyCommands(fonts);
});
Misc.getFontsList()
.then((fonts) => {
updateFontFamilyCommands(fonts);
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to update fonts commands")
);
});
Misc.getThemesList().then((themes) => {
updateThemesCommands(themes);
});
Misc.getThemesList()
.then((themes) => {
updateThemesCommands(themes);
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to update themes commands")
);
});
Misc.getChallengeList().then((challenges) => {
updateLoadChallengeCommands(challenges);
});
Misc.getChallengeList()
.then((challenges) => {
updateLoadChallengeCommands(challenges);
})
.catch((e) => {
console.error(
Misc.createErrorMessage(e, "Failed to update challenges commands")
);
});
export const commands: MonkeyTypes.CommandsSubgroup = {
title: "",

View file

@ -2,7 +2,6 @@ import * as ThemeController from "../controllers/theme-controller";
import Config, * as UpdateConfig from "../config";
import * as Focus from "../test/focus";
import * as CommandlineLists from "./commands";
import * as Misc from "./../utils/misc";
import * as TestUI from "../test/test-ui";
import * as DB from "../db";
import * as Notifications from "../elements/notifications";
@ -14,7 +13,6 @@ import { Auth } from "../firebase";
import { isAnyPopupVisible } from "../utils/misc";
import { update as updateCustomThemesList } from "./lists/custom-themes-list";
import { update as updateTagsCommands } from "./lists/tags";
import { update as updateThemesCommands } from "./lists/themes";
let commandLineMouseMode = false;
let themeChosen = false;
@ -191,9 +189,6 @@ export let show = (): void => {
);
}
$("#commandLine input").val("");
Misc.getThemesList().then((themes) => {
updateThemesCommands(themes);
});
updateSuggested();
$("#commandLine input").trigger("focus");
};

View file

@ -116,6 +116,16 @@ export async function isConfigValueValidAsync(
if (layoutNames.length < 2 || layoutNames.length > 5) break;
try {
await Misc.getLayoutsList();
} catch (e) {
customMessage = Misc.createErrorMessage(
e,
"Failed to validate layoutfluid value"
);
break;
}
// convert the layout names to layouts
const layouts = await Promise.all(
layoutNames.map(async (layoutName) => Misc.getLayout(layoutName))

View file

@ -117,17 +117,26 @@ export async function getDataAndInit(): Promise<boolean> {
ResultFilters.loadTags(snapshot.tags);
Promise.all([Misc.getLanguageList(), Misc.getFunboxList()]).then((values) => {
const [languages, funboxes] = values;
languages.forEach((language) => {
ResultFilters.defaultResultFilters.language[language] = true;
Promise.all([Misc.getLanguageList(), Misc.getFunboxList()])
.then((values) => {
const [languages, funboxes] = values;
languages.forEach((language) => {
ResultFilters.defaultResultFilters.language[language] = true;
});
funboxes.forEach((funbox) => {
ResultFilters.defaultResultFilters.funbox[funbox.name] = true;
});
// filters = defaultResultFilters;
ResultFilters.load();
})
.catch((e) => {
console.log(
Misc.createErrorMessage(
e,
"Something went wrong while loading the filters"
)
);
});
funboxes.forEach((funbox) => {
ResultFilters.defaultResultFilters.funbox[funbox.name] = true;
});
// filters = defaultResultFilters;
ResultFilters.load();
});
if (snapshot.needsToChangeName) {
Notifications.addBanner(

View file

@ -170,7 +170,20 @@ export async function setup(challengeName: string): Promise<boolean> {
UpdateConfig.setFunbox("none");
const list = await Misc.getChallengeList();
let list;
try {
list = await Misc.getChallengeList();
} catch (e) {
const message = Misc.createErrorMessage(e, "Failed to setup challenge");
Notifications.add(message, -1);
ManualRestart.set();
setTimeout(() => {
$("#top .config").removeClass("hidden");
$(".page.pageTest").removeClass("hidden");
}, 250);
return false;
}
const challenge = list.filter((c) => c.name === challengeName)[0];
let notitext;
try {

View file

@ -206,7 +206,16 @@ export function clearPreview(applyTheme = true): void {
let themesList: string[] = [];
async function changeThemeList(): Promise<void> {
const themes = await Misc.getThemesList();
let themes;
try {
themes = await Misc.getThemesList();
} catch (e) {
console.error(
Misc.createErrorMessage(e, "Failed to update random theme list")
);
return;
}
if (Config.randomTheme === "fav" && Config.favThemes.length > 0) {
themesList = Config.favThemes;
} else if (Config.randomTheme === "light") {

View file

@ -4,8 +4,10 @@ import * as SlowTimer from "../states/slow-timer";
import * as ConfigEvent from "../observables/config-event";
import * as Misc from "../utils/misc";
import * as Hangul from "hangul-js";
import * as Notifications from "../elements/notifications";
import * as ActivePage from "../states/active-page";
export function highlightKey(currentKey: string): void {
if (Config.mode === "zen") return;
if (currentKey === "") currentKey = " ";
@ -103,7 +105,17 @@ export async function refresh(
if (ActivePage.get() !== "test") return;
if (!layoutName) return;
try {
const layouts = await Misc.getLayoutsList();
let layouts;
try {
layouts = await Misc.getLayoutsList();
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to refresh keymap"),
-1
);
return;
}
let lts = layouts[layoutName]; //layout to show
let layoutString = layoutName;
if (Config.keymapLayout === "overrideSync") {

View file

@ -96,8 +96,27 @@ async function getStatsAndHistogramData(): Promise<void> {
}
async function fill(): Promise<void> {
const supporters = await Misc.getSupportersList();
const contributors = await Misc.getContributorsList();
let supporters: string[];
try {
supporters = await Misc.getSupportersList();
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to get supporters"),
-1
);
supporters = [];
}
let contributors: string[];
try {
contributors = await Misc.getContributorsList();
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to get contributors"),
-1
);
contributors = [];
}
await getStatsAndHistogramData();
updateStatsAndHistogram();

View file

@ -408,17 +408,31 @@ export async function fillSettingsPage(): Promise<void> {
// Language Selection Combobox
const languageEl = $(".pageSettings .section.language select").empty();
const languageGroups = await Misc.getLanguageGroups();
languageGroups.forEach((group) => {
let langComboBox = `<optgroup label="${group.name}">`;
group.languages.forEach((language: string) => {
langComboBox += `<option value="${language}">
let languageGroups;
try {
languageGroups = await Misc.getLanguageGroups();
} catch (e) {
console.error(
Misc.createErrorMessage(
e,
"Failed to initialize settings language picker"
)
);
}
if (languageGroups) {
languageGroups.forEach((group) => {
let langComboBox = `<optgroup label="${group.name}">`;
group.languages.forEach((language: string) => {
langComboBox += `<option value="${language}">
${language.replace(/_/g, " ")}
</option>`;
});
langComboBox += `</optgroup>`;
languageEl.append(langComboBox);
});
langComboBox += `</optgroup>`;
languageEl.append(langComboBox);
});
}
languageEl.select2({
width: "100%",
});
@ -427,18 +441,28 @@ export async function fillSettingsPage(): Promise<void> {
layoutEl.append(`<option value='default'>off</option>`);
const keymapEl = $(".pageSettings .section.keymapLayout select").empty();
keymapEl.append(`<option value='overrideSync'>emulator sync</option>`);
Object.keys(await Misc.getLayoutsList()).forEach((layout) => {
if (layout.toString() !== "korean") {
layoutEl.append(
`<option value='${layout}'>${layout.replace(/_/g, " ")}</option>`
);
}
if (layout.toString() != "default") {
keymapEl.append(
`<option value='${layout}'>${layout.replace(/_/g, " ")}</option>`
);
}
});
let layoutsList;
try {
layoutsList = await Misc.getLayoutsList();
} catch (e) {
console.error(Misc.createErrorMessage(e, "Failed to refresh keymap"));
}
if (layoutsList) {
Object.keys(layoutsList).forEach((layout) => {
if (layout.toString() !== "korean") {
layoutEl.append(
`<option value='${layout}'>${layout.replace(/_/g, " ")}</option>`
);
}
if (layout.toString() != "default") {
keymapEl.append(
`<option value='${layout}'>${layout.replace(/_/g, " ")}</option>`
);
}
});
}
layoutEl.select2({
width: "100%",
});
@ -452,14 +476,32 @@ export async function fillSettingsPage(): Promise<void> {
const themeEl2 = $(
".pageSettings .section.autoSwitchThemeInputs select.dark"
).empty();
for (const theme of await Misc.getThemesList()) {
themeEl1.append(
`<option value='${theme.name}'>${theme.name.replace(/_/g, " ")}</option>`
);
themeEl2.append(
`<option value='${theme.name}'>${theme.name.replace(/_/g, " ")}</option>`
let themes;
try {
themes = await Misc.getThemesList();
} catch (e) {
console.error(
Misc.createErrorMessage(e, "Failed to load themes into dropdown boxes")
);
}
if (themes) {
for (const theme of themes) {
themeEl1.append(
`<option value='${theme.name}'>${theme.name.replace(
/_/g,
" "
)}</option>`
);
themeEl2.append(
`<option value='${theme.name}'>${theme.name.replace(
/_/g,
" "
)}</option>`
);
}
}
themeEl1.select2({
width: "100%",
});
@ -476,57 +518,71 @@ export async function fillSettingsPage(): Promise<void> {
const funboxEl = $(".pageSettings .section.funbox .buttons").empty();
funboxEl.append(`<div class="funbox button" funbox='none'>none</div>`);
Misc.getFunboxList().then((funboxModes) => {
funboxModes.forEach((funbox) => {
if (funbox.name === "mirror") {
funboxEl.append(
`<div class="funbox button" funbox='${funbox.name}' aria-label="${
funbox.info
}" data-balloon-pos="up" data-balloon-length="fit" type="${
funbox.type
}" style="transform:scaleX(-1);">${funbox.name.replace(
/_/g,
" "
)}</div>`
);
} else {
funboxEl.append(
`<div class="funbox button" funbox='${funbox.name}' aria-label="${
funbox.info
}" data-balloon-pos="up" data-balloon-length="fit" type="${
funbox.type
}">${funbox.name.replace(/_/g, " ")}</div>`
);
}
});
});
let isCustomFont = true;
const fontsEl = $(".pageSettings .section.fontFamily .buttons").empty();
Misc.getFontsList().then((fonts) => {
fonts.forEach((font) => {
if (Config.fontFamily === font.name) isCustomFont = false;
fontsEl.append(
`<div class="button${
Config.fontFamily === font.name ? " active" : ""
}" style="font-family:${
font.display !== undefined ? font.display : font.name
}" fontFamily="${font.name.replace(/ /g, "_")}" tabindex="0"
onclick="this.blur();">${
font.display !== undefined ? font.display : font.name
}</div>`
Misc.getFunboxList()
.then((funboxModes) => {
funboxModes.forEach((funbox) => {
if (funbox.name === "mirror") {
funboxEl.append(
`<div class="funbox button" funbox='${funbox.name}' aria-label="${
funbox.info
}" data-balloon-pos="up" data-balloon-length="fit" type="${
funbox.type
}" style="transform:scaleX(-1);">${funbox.name.replace(
/_/g,
" "
)}</div>`
);
} else {
funboxEl.append(
`<div class="funbox button" funbox='${funbox.name}' aria-label="${
funbox.info
}" data-balloon-pos="up" data-balloon-length="fit" type="${
funbox.type
}">${funbox.name.replace(/_/g, " ")}</div>`
);
}
});
})
.catch((e) => {
Notifications.add(
Misc.createErrorMessage(e, "Failed to update funbox settings buttons"),
-1
);
});
fontsEl.append(
isCustomFont
? `<div class="button no-auto-handle custom active" onclick="this.blur();">Custom (${Config.fontFamily.replace(
/_/g,
" "
)})</div>`
: '<div class="button no-auto-handle custom" onclick="this.blur();">Custom</div>'
);
});
let isCustomFont = true;
const fontsEl = $(".pageSettings .section.fontFamily .buttons").empty();
Misc.getFontsList()
.then((fonts) => {
fonts.forEach((font) => {
if (Config.fontFamily === font.name) isCustomFont = false;
fontsEl.append(
`<div class="button${
Config.fontFamily === font.name ? " active" : ""
}" style="font-family:${
font.display !== undefined ? font.display : font.name
}" fontFamily="${font.name.replace(/ /g, "_")}" tabindex="0"
onclick="this.blur();">${
font.display !== undefined ? font.display : font.name
}</div>`
);
});
fontsEl.append(
isCustomFont
? `<div class="button no-auto-handle custom active" onclick="this.blur();">Custom (${Config.fontFamily.replace(
/_/g,
" "
)})</div>`
: '<div class="button no-auto-handle custom" onclick="this.blur();">Custom</div>'
);
})
.catch((e) => {
Notifications.add(
Misc.createErrorMessage(e, "Failed to update fonts settings buttons"),
-1
);
});
$(".pageSettings .section.customBackgroundSize input").val(
Config.customBackground

View file

@ -1,12 +1,26 @@
import * as Misc from "../utils/misc";
import * as CustomText from "../test/custom-text";
import * as Notifications from "../elements/notifications";
let initialised = false;
async function init(): Promise<void> {
if (!initialised) {
$("#wordFilterPopup .languageInput").empty();
const LanguageList = await Misc.getLanguageList();
let LanguageList;
try {
LanguageList = await Misc.getLanguageList();
} catch (e) {
console.error(
Misc.createErrorMessage(
e,
"Failed to initialise word filter popup language list"
)
);
return;
}
LanguageList.forEach((language) => {
let prettyLang = language;
prettyLang = prettyLang.replace("_", " ");
@ -42,7 +56,18 @@ async function filter(language: string): Promise<string[]> {
filterout = filterout.replace(/\s+/gi, "|");
const regexcl = new RegExp(filterout, "i");
const filteredWords = [];
const languageWordList = await Misc.getLanguage(language);
let languageWordList;
try {
languageWordList = await Misc.getLanguage(language);
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to filter language words"),
-1
);
return [];
}
const maxLengthInput = $("#wordFilterPopup .wordMaxInput").val() as string;
const minLengthInput = $("#wordFilterPopup .wordMinInput").val() as string;
let maxLength;

View file

@ -1,50 +0,0 @@
import * as Misc from "../utils/misc";
import Config, * as UpdateConfig from "../config";
export async function setActiveGroup(
groupName: string | undefined,
clicked: boolean | undefined = false
): Promise<void> {
let currentGroup: MonkeyTypes.LanguageGroup | undefined;
if (groupName === undefined) {
currentGroup = await Misc.findCurrentGroup(Config.language);
} else {
const groups: MonkeyTypes.LanguageGroup[] = await Misc.getLanguageGroups();
groups.forEach((g: MonkeyTypes.LanguageGroup) => {
if (g.name === groupName) {
currentGroup = g;
}
});
}
$(`.pageSettings .section.languageGroups .button`).removeClass("active");
if (currentGroup === undefined) return;
$(
`.pageSettings .section.languageGroups .button[group='${currentGroup.name}']`
).addClass("active");
const langElement: JQuery<HTMLElement> = $(
".pageSettings .section.language .buttons"
).empty();
currentGroup.languages.forEach((langName: string) => {
langElement.append(
`<div class="language button" language='${langName}'>
${langName.replace(/_/g, " ")}
</div>`
);
});
if (clicked) {
$($(`.pageSettings .section.language .buttons .button`)[0]).addClass(
"active"
);
UpdateConfig.setLanguage(currentGroup.languages[0]);
} else {
$(
`.pageSettings .section.language .buttons .button[language=${Config.language}]`
).addClass("active");
}
}

View file

@ -158,7 +158,17 @@ export async function refreshButtons(): Promise<void> {
activeThemeName = ThemeController.randomTheme as string;
}
const themes = await Misc.getSortedThemesList();
let themes;
try {
themes = await Misc.getSortedThemesList();
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to refresh theme buttons"),
-1
);
return;
}
//first show favourites
if (Config.favThemes.length > 0) {
favThemesEl.css({ paddingBottom: "1rem" });

View file

@ -120,13 +120,37 @@ export async function activate(funbox?: string): Promise<boolean | undefined> {
if (funbox === undefined || funbox === null) {
funbox = Config.funbox;
}
const funboxInfo = await Misc.getFunbox(funbox);
let funboxInfo;
try {
funboxInfo = await Misc.getFunbox(funbox);
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to activate funbox"),
-1
);
UpdateConfig.setFunbox("none", true);
await clear();
return false;
}
$("#funBoxTheme").attr("href", ``);
$("#words").removeClass("nospace");
$("#words").removeClass("arrows");
if ((await Misc.getCurrentLanguage(Config.language)).ligatures) {
let language;
try {
language = await Misc.getCurrentLanguage(Config.language);
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to activate funbox"),
-1
);
UpdateConfig.setFunbox("none", true);
await clear();
return false;
}
if (language.ligatures) {
if (funbox == "choo_choo" || funbox == "earthquake") {
Notifications.add(
"Current language does not support this funbox mode",
@ -162,7 +186,17 @@ export async function activate(funbox?: string): Promise<boolean | undefined> {
(funbox !== "none" && mode === undefined) ||
(funbox !== "none" && mode === null)
) {
const list = await Misc.getFunboxList();
let list;
try {
list = await Misc.getFunboxList();
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to activate funbox"),
-1
);
await clear();
return;
}
mode = list.filter((f) => f.name === funbox)[0].type;
}
@ -228,7 +262,17 @@ export async function rememberSettings(): Promise<void> {
(funbox !== "none" && mode === undefined) ||
(funbox !== "none" && mode === null)
) {
const list = await Misc.getFunboxList();
let list;
try {
list = await Misc.getFunboxList();
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to remember setting"),
-1
);
await clear();
return;
}
mode = list.filter((f) => f.name === funbox)[0].type;
}
if (mode === "style") {

View file

@ -1,6 +1,7 @@
import Config from "../config";
import * as Misc from "../utils/misc";
import { capsState } from "./caps-warning";
import * as Notifications from "../elements/notifications";
export async function getCharFromEvent(
event: JQuery.KeyDownEvent
@ -13,7 +14,17 @@ export async function getCharFromEvent(
return event.shiftKey;
}
const layout = await Misc.getLayout(Config.layout);
let layout;
try {
layout = await Misc.getLayout(Config.layout);
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to emulate event"),
-1
);
return event.key;
}
let keyEventCodes: string[] = [];

View file

@ -1,6 +1,7 @@
import Config from "../config";
import * as Misc from "../utils/misc";
import { capsState } from "./caps-warning";
import * as Notifications from "../elements/notifications";
export let leftState = false;
export let rightState = false;
@ -50,9 +51,16 @@ function dynamicKeymapLegendStyle(uppercase: boolean): void {
async function buildKeymapStrings(): Promise<void> {
if (keymapStrings.keymap === Config.keymapLayout) return;
const layout = await Misc.getLayout(Config.keymapLayout);
if (layout === undefined) return;
let layout;
try {
layout = await Misc.getLayout(Config.keymapLayout);
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to track shift state"),
-1
);
return;
}
const layoutKeys = layout.keys;
const layoutKeysEntries = Object.entries(layoutKeys) as [string, string[]][];

View file

@ -892,19 +892,42 @@ export async function init(): Promise<void> {
if (Config.quoteLength.includes(-3) && !Auth?.currentUser) {
UpdateConfig.setQuoteLength(-1);
}
let language = await Misc.getLanguage(Config.language);
let language;
try {
language = await Misc.getLanguage(Config.language);
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to load language"),
-1
);
}
if (language && language.name !== Config.language) {
UpdateConfig.setLanguage("english");
}
if (!language) {
UpdateConfig.setLanguage("english");
language = await Misc.getLanguage(Config.language);
try {
language = await Misc.getLanguage(Config.language);
} catch (e) {
Notifications.add(
Misc.createErrorMessage(e, "Failed to load language"),
-1
);
return;
}
}
if (Config.mode === "quote") {
const group = await Misc.findCurrentGroup(Config.language);
let group;
try {
group = await Misc.findCurrentGroup(Config.language);
} catch (e) {
console.error(
Misc.createErrorMessage(e, "Failed to find current language group")
);
return;
}
if (group && group.name !== "code" && group.name !== Config.language) {
UpdateConfig.setLanguage(group.name);
}

View file

@ -60,7 +60,16 @@ export async function getSection(language: string): Promise<Section> {
// get TLD for wikipedia according to language group
let urlTLD = "en";
const currentLanguageGroup = await Misc.findCurrentGroup(language);
let currentLanguageGroup;
try {
currentLanguageGroup = await Misc.findCurrentGroup(language);
} catch (e) {
console.error(
Misc.createErrorMessage(e, "Failed to find current language group")
);
}
if (currentLanguageGroup !== undefined) {
urlTLD = await getTLD(currentLanguageGroup);
}

View file

@ -1,5 +1,240 @@
import * as Loader from "../elements/loader";
async function fetchJson<T>(url: string): Promise<T> {
try {
if (!url) throw new Error("No URL");
const res = await fetch(url);
if (res.ok) {
return await res.json();
} else {
throw new Error(`${res.status} ${res.statusText}`);
}
} catch (e) {
console.error("Error fetching JSON: " + url, e);
throw e;
}
}
export const cachedFetchJson = memoizeAsync(fetchJson);
export async function getLayoutsList(): Promise<MonkeyTypes.Layouts> {
try {
const layoutsList = await cachedFetchJson<MonkeyTypes.Layouts>(
"/./layouts/_list.json"
);
return layoutsList;
} catch (e) {
throw new Error("Layouts JSON fetch failed");
}
}
/**
* @throws {Error} If layout list or layout doesnt exist.
*/
export async function getLayout(
layoutName: string
): Promise<MonkeyTypes.Layout> {
const layouts = await getLayoutsList();
const layout = layouts[layoutName];
if (layout === undefined) {
throw new Error(`Layout ${layoutName} is undefined`);
}
return layout;
}
let themesList: MonkeyTypes.Theme[] | undefined;
export async function getThemesList(): Promise<MonkeyTypes.Theme[]> {
if (!themesList) {
let themes = await cachedFetchJson<MonkeyTypes.Theme[]>(
"/./themes/_list.json"
);
themes = themes.sort(function (a: MonkeyTypes.Theme, b: MonkeyTypes.Theme) {
const nameA = a.name.toLowerCase();
const nameB = b.name.toLowerCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
});
themesList = themes;
return themesList;
} else {
return themesList;
}
}
let sortedThemesList: MonkeyTypes.Theme[] | undefined;
export async function getSortedThemesList(): Promise<MonkeyTypes.Theme[]> {
if (!sortedThemesList) {
if (!themesList) {
await getThemesList();
}
if (!themesList) {
throw new Error("Themes list is undefined");
}
let sorted = [...themesList];
sorted = sorted.sort((a, b) => {
const b1 = hexToHSL(a.bgColor);
const b2 = hexToHSL(b.bgColor);
return b2.lgt - b1.lgt;
});
sortedThemesList = sorted;
return sortedThemesList;
} else {
return sortedThemesList;
}
}
export async function getLanguageList(): Promise<string[]> {
try {
const languageList = await cachedFetchJson<string[]>(
"/./languages/_list.json"
);
return languageList;
} catch (e) {
throw new Error("Language list JSON fetch failed");
}
}
export async function getLanguageGroups(): Promise<
MonkeyTypes.LanguageGroup[]
> {
try {
const languageGroupList = await cachedFetchJson<
MonkeyTypes.LanguageGroup[]
>("/./languages/_groups.json");
return languageGroupList;
} catch (e) {
throw new Error("Language groups JSON fetch failed");
}
}
let currentLanguage: MonkeyTypes.LanguageObject;
export async function getLanguage(
lang: string
): Promise<MonkeyTypes.LanguageObject> {
// try {
if (currentLanguage == undefined || currentLanguage.name !== lang) {
currentLanguage = await cachedFetchJson<MonkeyTypes.LanguageObject>(
`/./languages/${lang}.json`
);
}
return currentLanguage;
// } catch (e) {
// console.error(`error getting language`);
// console.error(e);
// currentLanguage = await cachedFetchJson<MonkeyTypes.LanguageObject>(
// `/./language/english.json`
// );
// return currentLanguage;
// }
}
export async function getCurrentLanguage(
languageName: string
): Promise<MonkeyTypes.LanguageObject> {
return await getLanguage(languageName);
}
export async function findCurrentGroup(
language: string
): Promise<MonkeyTypes.LanguageGroup | undefined> {
let retgroup: MonkeyTypes.LanguageGroup | undefined;
const groups = await getLanguageGroups();
groups.forEach((group) => {
if (retgroup === undefined) {
if (group.languages.includes(language)) {
retgroup = group;
}
}
});
return retgroup;
}
let funboxList: MonkeyTypes.FunboxObject[] | undefined;
export async function getFunboxList(): Promise<MonkeyTypes.FunboxObject[]> {
if (!funboxList) {
let list = await cachedFetchJson<MonkeyTypes.FunboxObject[]>(
"/./funbox/_list.json"
);
list = list.sort(function (
a: MonkeyTypes.FunboxObject,
b: MonkeyTypes.FunboxObject
) {
const nameA = a.name.toLowerCase();
const nameB = b.name.toLowerCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
});
funboxList = list;
return funboxList;
} else {
return funboxList;
}
}
export async function getFunbox(
funbox: string
): Promise<MonkeyTypes.FunboxObject | undefined> {
const list: MonkeyTypes.FunboxObject[] = await getFunboxList();
return list.find(function (element) {
return element.name == funbox;
});
}
let fontsList: MonkeyTypes.FontObject[] | undefined;
export async function getFontsList(): Promise<MonkeyTypes.FontObject[]> {
if (!fontsList) {
let list = await cachedFetchJson<MonkeyTypes.FontObject[]>(
"/./fonts/_list.json"
);
list = list.sort(function (
a: MonkeyTypes.FontObject,
b: MonkeyTypes.FontObject
) {
const nameA = a.name.toLowerCase();
const nameB = b.name.toLowerCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
});
fontsList = list;
return fontsList;
} else {
return fontsList;
}
}
export async function getChallengeList(): Promise<MonkeyTypes.Challenge[]> {
try {
const data = await cachedFetchJson<MonkeyTypes.Challenge[]>(
"/./challenges/_list.json"
);
return data;
} catch (e) {
throw new Error("Challenge list JSON fetch failed");
}
}
export async function getSupportersList(): Promise<string[]> {
try {
const data = await cachedFetchJson<string[]>("/./about/supporters.json");
return data;
} catch (e) {
throw new Error("Supporters list JSON fetch failed");
}
}
export async function getContributorsList(): Promise<string[]> {
try {
const data = await cachedFetchJson<string[]>("/./about/contributors.json");
return data;
} catch (e) {
throw new Error("Contributors list JSON fetch failed");
}
}
function hexToHSL(hex: string): {
hue: number;
sat: number;
@ -66,224 +301,6 @@ export function isColorDark(hex: string): boolean {
return hsl.lgt < 50;
}
let themesList: MonkeyTypes.Theme[] = [];
export async function getThemesList(): Promise<MonkeyTypes.Theme[]> {
if (themesList.length == 0) {
return $.getJSON("/./themes/_list.json", function (data) {
const list = data.sort(function (
a: MonkeyTypes.Theme,
b: MonkeyTypes.Theme
) {
const nameA = a.name.toLowerCase();
const nameB = b.name.toLowerCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
});
themesList = list;
return themesList;
});
} else {
return themesList;
}
}
let sortedThemesList: MonkeyTypes.Theme[] = [];
export async function getSortedThemesList(): Promise<MonkeyTypes.Theme[]> {
if (sortedThemesList.length === 0) {
if (themesList.length === 0) {
await getThemesList();
}
let sorted = [...themesList];
sorted = sorted.sort((a, b) => {
const b1 = hexToHSL(a.bgColor);
const b2 = hexToHSL(b.bgColor);
return b2.lgt - b1.lgt;
});
sortedThemesList = sorted;
return sortedThemesList;
} else {
return sortedThemesList;
}
}
let funboxList: MonkeyTypes.FunboxObject[] = [];
export async function getFunboxList(): Promise<MonkeyTypes.FunboxObject[]> {
if (funboxList.length === 0) {
return $.getJSON("/./funbox/_list.json", function (data) {
funboxList = data.sort(function (
a: MonkeyTypes.FunboxObject,
b: MonkeyTypes.FunboxObject
) {
const nameA = a.name.toLowerCase();
const nameB = b.name.toLowerCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
});
return funboxList;
});
} else {
return funboxList;
}
}
export async function getFunbox(
funbox: string
): Promise<MonkeyTypes.FunboxObject | undefined> {
const list: MonkeyTypes.FunboxObject[] = await getFunboxList();
return list.find(function (element) {
return element.name == funbox;
});
}
let layoutsList: MonkeyTypes.Layouts = {};
export async function getLayoutsList(): Promise<MonkeyTypes.Layouts> {
if (Object.keys(layoutsList).length === 0) {
return $.getJSON("/./layouts/_list.json", function (data) {
layoutsList = data;
return layoutsList;
});
} else {
return layoutsList;
}
}
export async function getLayout(
layoutName: string
): Promise<MonkeyTypes.Layout> {
if (Object.keys(layoutsList).length === 0) {
await getLayoutsList();
}
return layoutsList[layoutName];
}
let fontsList: MonkeyTypes.FontObject[] = [];
export async function getFontsList(): Promise<MonkeyTypes.FontObject[]> {
if (fontsList.length === 0) {
return $.getJSON("/./fonts/_list.json", function (data) {
fontsList = data.sort(function (
a: MonkeyTypes.FontObject,
b: MonkeyTypes.FontObject
) {
const nameA = a.name.toLowerCase();
const nameB = b.name.toLowerCase();
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return 0;
});
return fontsList;
});
} else {
return fontsList;
}
}
let supportersList: string[] = [];
export async function getSupportersList(): Promise<string[]> {
if (supportersList.length === 0) {
return $.getJSON("/./about/supporters.json", function (data) {
supportersList = data;
return supportersList;
});
} else {
return supportersList;
}
}
let contributorsList: string[] = [];
export async function getContributorsList(): Promise<string[]> {
if (contributorsList.length === 0) {
return $.getJSON("/./about/contributors.json", function (data) {
contributorsList = data;
return contributorsList;
});
} else {
return contributorsList;
}
}
let languageList: string[] = [];
export async function getLanguageList(): Promise<string[]> {
if (languageList.length === 0) {
return $.getJSON("/./languages/_list.json", function (data) {
languageList = data;
return languageList;
});
} else {
return languageList;
}
}
let languageGroupList: MonkeyTypes.LanguageGroup[] = [];
export async function getLanguageGroups(): Promise<
MonkeyTypes.LanguageGroup[]
> {
if (languageGroupList.length === 0) {
return $.getJSON("/./languages/_groups.json", function (data) {
languageGroupList = data;
return languageGroupList;
});
} else {
return languageGroupList;
}
}
let currentLanguage: MonkeyTypes.LanguageObject;
export async function getLanguage(
lang: string
): Promise<MonkeyTypes.LanguageObject> {
try {
if (currentLanguage == undefined || currentLanguage.name !== lang) {
console.log("getting language json");
await $.getJSON(`/./languages/${lang}.json`, function (data) {
currentLanguage = data;
});
}
return currentLanguage;
} catch (e) {
console.error(`error getting language`);
console.error(e);
await $.getJSON(`/./languages/english.json`, function (data) {
currentLanguage = data;
});
return currentLanguage;
}
}
export async function getCurrentLanguage(
languageName: string
): Promise<MonkeyTypes.LanguageObject> {
return await getLanguage(languageName);
}
export async function findCurrentGroup(
language: string
): Promise<MonkeyTypes.LanguageGroup | undefined> {
let retgroup: MonkeyTypes.LanguageGroup | undefined;
const groups = await getLanguageGroups();
groups.forEach((group) => {
if (retgroup === undefined) {
if (group.languages.includes(language)) {
retgroup = group;
}
}
});
return retgroup;
}
let challengeList: MonkeyTypes.Challenge[] = [];
export async function getChallengeList(): Promise<MonkeyTypes.Challenge[]> {
if (challengeList.length === 0) {
return $.getJSON("/./challenges/_list.json", function (data) {
challengeList = data;
return challengeList;
});
} else {
return challengeList;
}
}
export function smooth(
arr: number[],
windowSize: number,
@ -1244,6 +1261,30 @@ export async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function memoizeAsync<T extends (...args: any) => Promise<any>>(
fn: T,
getKey?: (...args: Parameters<T>) => any
): T {
const cache = new Map<any, Promise<ReturnType<T>>>();
return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
const key = getKey ? getKey.apply(args) : args[0];
if (cache.has(key)) {
const ret = await cache.get(key);
if (ret !== undefined) {
return ret as ReturnType<T>;
}
}
// eslint-disable-next-line prefer-spread
const result = fn.apply(null, args);
cache.set(key, result);
return result;
}) as T;
}
export function isPasswordStrong(password: string): boolean {
const hasCapital = !!password.match(/[A-Z]/);
const hasNumber = !!password.match(/[\d]/);