diff --git a/src/mobx/stores/Auth.ts b/src/mobx/stores/Auth.ts index 1a4d8d09..9d61d956 100644 --- a/src/mobx/stores/Auth.ts +++ b/src/mobx/stores/Auth.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable } from "mobx"; +import { makeAutoObservable, ObservableMap } from "mobx"; import { Session } from "revolt-api/types/Auth"; import { Nullable } from "revolt.js/dist/util/null"; @@ -15,14 +15,14 @@ interface Data { * accounts and their sessions. */ export default class Auth implements Persistent { - private sessions: Record; + private sessions: ObservableMap; private current: Nullable; /** * Construct new Auth store. */ constructor() { - this.sessions = {}; + this.sessions = new ObservableMap(); this.current = null; makeAutoObservable(this); } @@ -37,8 +37,11 @@ export default class Auth implements Persistent { // eslint-disable-next-line require-jsdoc hydrate(data: Data) { - this.sessions = data.sessions; - if (data.current && this.sessions[data.current]) { + Object.keys(data.sessions).forEach((id) => + this.sessions.set(id, data.sessions[id]), + ); + + if (data.current && this.sessions.has(data.current)) { this.current = data.current; } } @@ -48,11 +51,7 @@ export default class Auth implements Persistent { * @param session Session */ setSession(session: Session) { - this.sessions = { - ...this.sessions, - [session.user_id]: session, - }; - + this.sessions.set(session.user_id, session); this.current = session.user_id; } diff --git a/src/mobx/stores/Experiments.ts b/src/mobx/stores/Experiments.ts new file mode 100644 index 00000000..bb0a35dc --- /dev/null +++ b/src/mobx/stores/Experiments.ts @@ -0,0 +1,86 @@ +import { action, computed, makeAutoObservable, ObservableSet } from "mobx"; + +import Persistent from "../Persistent"; + +export type Experiment = "search" | "theme_shop"; + +export const AVAILABLE_EXPERIMENTS: Experiment[] = ["theme_shop"]; + +export const EXPERIMENTS: { + [key in Experiment]: { title: string; description: string }; +} = { + search: { + title: "Search", + description: "Allows you to search for messages in channels.", + }, + theme_shop: { + title: "Theme Shop", + description: "Allows you to access and set user submitted themes.", + }, +}; + +interface Data { + enabled?: Experiment[]; +} + +/** + * Handles enabling and disabling client experiments. + */ +export default class Experiments implements Persistent { + private enabled: ObservableSet; + + /** + * Construct new Experiments store. + */ + constructor() { + this.enabled = new ObservableSet(); + makeAutoObservable(this); + } + + // eslint-disable-next-line require-jsdoc + toJSON() { + return { + enabled: this.enabled, + }; + } + + // eslint-disable-next-line require-jsdoc + @action hydrate(data: Data) { + if (data.enabled) { + for (const experiment of data.enabled) { + this.enabled.add(experiment as Experiment); + } + } + } + + /** + * Check if an experiment is enabled. + * @param experiment Experiment + */ + @computed isEnabled(experiment: Experiment) { + return this.enabled.has(experiment); + } + + /** + * Enable an experiment. + * @param experiment Experiment + */ + @action enable(experiment: Experiment) { + this.enabled.add(experiment); + } + + /** + * Disable an experiment. + * @param experiment Experiment + */ + @action disable(experiment: Experiment) { + this.enabled.delete(experiment); + } + + /** + * Reset and disable all experiments. + */ + @action reset() { + this.enabled.clear(); + } +} diff --git a/src/mobx/stores/LastOpened.ts b/src/mobx/stores/LastOpened.ts new file mode 100644 index 00000000..091bc012 --- /dev/null +++ b/src/mobx/stores/LastOpened.ts @@ -0,0 +1,57 @@ +import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; + +import Persistent from "../Persistent"; + +interface Data { + server?: Record; +} + +/** + * Keeps track of the last open channels, tabs, etc. + * Handles providing good UX experience on navigating + * back and forth between different parts of the app. + */ +export default class Experiments implements Persistent { + private server: ObservableMap; + + /** + * Construct new Experiments store. + */ + constructor() { + this.server = new ObservableMap(); + makeAutoObservable(this); + } + + // eslint-disable-next-line require-jsdoc + toJSON() { + return { + server: this.server, + }; + } + + // eslint-disable-next-line require-jsdoc + @action hydrate(data: Data) { + if (data.server) { + Object.keys(data.server).forEach((key) => + this.server.set(key, data.server![key]), + ); + } + } + + /** + * Get last opened channel in a server. + * @param server Server ID + */ + @computed get(server: string) { + return this.server.get(server); + } + + /** + * Set last opened channel in a server. + * @param server Server ID + * @param channel Channel ID + */ + @action enable(server: string, channel: string) { + this.server.set(server, channel); + } +} diff --git a/src/mobx/stores/LocaleOptions.ts b/src/mobx/stores/LocaleOptions.ts new file mode 100644 index 00000000..6f88d2f7 --- /dev/null +++ b/src/mobx/stores/LocaleOptions.ts @@ -0,0 +1,84 @@ +import { action, computed, makeAutoObservable } from "mobx"; + +import { Language, Languages } from "../../context/Locale"; + +import Persistent from "../Persistent"; + +interface Data { + lang: Language; +} + +/** + * Detect the browser language or match given language. + * @param lang Language to find + * @returns Matched Language + */ +export function findLanguage(lang?: string): Language { + if (!lang) { + if (typeof navigator === "undefined") { + lang = Language.ENGLISH; + } else { + lang = navigator.language; + } + } + + const code = lang.replace("-", "_"); + const short = code.split("_")[0]; + + const values = []; + for (const key in Language) { + const value = Language[key as keyof typeof Language]; + + // Skip alternative/joke languages + if (Languages[value].cat === "alt") continue; + + values.push(value); + if (value.startsWith(code)) { + return value as Language; + } + } + + for (const value of values.reverse()) { + if (value.startsWith(short)) { + return value as Language; + } + } + + return Language.ENGLISH; +} + +/** + * Keeps track of the last open channels, tabs, etc. + * Handles providing good UX experience on navigating + * back and forth between different parts of the app. + */ +export default class LocaleOptions implements Persistent { + private lang: Language; + + /** + * Construct new LocaleOptions store. + */ + constructor() { + this.lang = findLanguage(); + makeAutoObservable(this); + } + + // eslint-disable-next-line require-jsdoc + toJSON() { + return { + lang: this.lang, + }; + } + + // eslint-disable-next-line require-jsdoc + @action hydrate(data: Data) { + this.lang = data.lang; + } + + /** + * Get current language. + */ + @computed getLang() { + return this.lang; + } +}