feat(mobx): start work on settings store

This commit is contained in:
Paul 2021-12-12 23:55:58 +00:00
parent fef2c5997f
commit 26a34032f9
3 changed files with 111 additions and 22 deletions

View file

@ -4,11 +4,11 @@ import { createGlobalStyle } from "styled-components";
import { createContext } from "preact"; import { createContext } from "preact";
import { useEffect } from "preact/hooks"; import { useEffect } from "preact/hooks";
import { useApplicationState } from "../mobx/State";
import { getState } from "../redux";
import { connectState } from "../redux/connector"; import { connectState } from "../redux/connector";
import { Children } from "../types/Preact"; import { Children } from "../types/Preact";
import { fetchManifest, fetchTheme } from "../pages/settings/panes/ThemeShop";
import { getState } from "../redux";
export type Variables = export type Variables =
| "accent" | "accent"
@ -57,6 +57,7 @@ export type Fonts =
| "Raleway" | "Raleway"
| "Ubuntu" | "Ubuntu"
| "Comic Neue"; | "Comic Neue";
export type MonospaceFonts = export type MonospaceFonts =
| "Fira Code" | "Fira Code"
| "Roboto Mono" | "Roboto Mono"
@ -285,23 +286,23 @@ export const PRESETS: Record<string, Theme> = {
// todo: store used themes locally // todo: store used themes locally
export function getBaseTheme(name: string): Theme { export function getBaseTheme(name: string): Theme {
if (name in PRESETS) { if (name in PRESETS) {
return PRESETS[name] return PRESETS[name];
} }
// TODO: properly initialize `themes` in state instead of letting it be undefined // TODO: properly initialize `themes` in state instead of letting it be undefined
const themes = getState().themes ?? {} const themes = getState().themes ?? {};
if (name in themes) { if (name in themes) {
const { theme } = themes[name]; const { theme } = themes[name];
return { return {
...PRESETS[theme.light ? 'light' : 'dark'], ...PRESETS[theme.light ? "light" : "dark"],
...theme ...theme,
} };
} }
// how did we get here // how did we get here
return PRESETS['dark'] return PRESETS["dark"];
} }
const keys = Object.keys(PRESETS.dark); const keys = Object.keys(PRESETS.dark);
@ -315,21 +316,22 @@ export const generateVariables = (theme: Theme) => {
return (Object.keys(theme) as Variables[]).map((key) => { return (Object.keys(theme) as Variables[]).map((key) => {
if (!keys.includes(key)) return; if (!keys.includes(key)) return;
return `--${key}: ${theme[key]};`; return `--${key}: ${theme[key]};`;
}) });
} };
// Load the default default them and apply extras later // Load the default default them and apply extras later
export const ThemeContext = createContext<Theme>(PRESETS["dark"]); export const ThemeContext = createContext<Theme>(PRESETS["dark"]);
interface Props { interface Props {
children: Children; children: Children;
options?: ThemeOptions;
} }
function Theme({ children, options }: Props) { export default function Theme({ children }: Props) {
const settings = useApplicationState().settings;
const theme: Theme = { const theme: Theme = {
...getBaseTheme(options?.base ?? 'dark'), ...getBaseTheme(settings.get("appearance:theme:base") ?? "dark"),
...options?.custom, ...settings.get("appearance:theme:custom"),
}; };
const root = document.documentElement.style; const root = document.documentElement.style;
@ -346,8 +348,11 @@ function Theme({ children, options }: Props) {
}, [root, theme.monospaceFont]); }, [root, theme.monospaceFont]);
useEffect(() => { useEffect(() => {
root.setProperty("--ligatures", options?.ligatures ? "normal" : "none"); root.setProperty(
}, [root, options?.ligatures]); "--ligatures",
settings.get("appearance:ligatures") ? "normal" : "none",
);
}, [root, settings.get("appearance:ligatures")]);
useEffect(() => { useEffect(() => {
const resize = () => const resize = () =>
@ -371,9 +376,3 @@ function Theme({ children, options }: Props) {
</ThemeContext.Provider> </ThemeContext.Provider>
); );
} }
export default connectState<{ children: Children }>(Theme, (state) => {
return {
options: state.settings.theme,
};
});

View file

@ -13,6 +13,7 @@ import LocaleOptions from "./stores/LocaleOptions";
import MessageQueue from "./stores/MessageQueue"; import MessageQueue from "./stores/MessageQueue";
import NotificationOptions from "./stores/NotificationOptions"; import NotificationOptions from "./stores/NotificationOptions";
import ServerConfig from "./stores/ServerConfig"; import ServerConfig from "./stores/ServerConfig";
import Settings from "./stores/Settings";
/** /**
* Handles global application state. * Handles global application state.
@ -26,6 +27,7 @@ export default class State {
config: ServerConfig; config: ServerConfig;
notifications: NotificationOptions; notifications: NotificationOptions;
queue: MessageQueue; queue: MessageQueue;
settings: Settings;
private persistent: [string, Persistent<unknown>][] = []; private persistent: [string, Persistent<unknown>][] = [];
@ -41,6 +43,7 @@ export default class State {
this.config = new ServerConfig(); this.config = new ServerConfig();
this.notifications = new NotificationOptions(); this.notifications = new NotificationOptions();
this.queue = new MessageQueue(); this.queue = new MessageQueue();
this.settings = new Settings();
makeAutoObservable(this); makeAutoObservable(this);
this.registerListeners = this.registerListeners.bind(this); this.registerListeners = this.registerListeners.bind(this);

View file

@ -0,0 +1,87 @@
import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
import { mapToRecord } from "../../lib/conversion";
import { Theme } from "../../context/Theme";
import { Sounds } from "../../assets/sounds/Audio";
import Persistent from "../interfaces/Persistent";
import Store from "../interfaces/Store";
export type SoundOptions = {
[key in Sounds]?: boolean;
};
export type EmojiPack = "mutant" | "twemoji" | "noto" | "openmoji";
interface ISettings {
"notifications:desktop": boolean;
"notifications:sounds": SoundOptions;
"appearance:emoji": EmojiPack;
"appearance:ligatures": boolean;
"appearance:theme:base": string;
"appearance:theme:custom": Partial<Theme>;
}
/*const Schema: {
[key in keyof ISettings]:
| "string"
| "number"
| "boolean"
| "object"
| "function";
} = {
"notifications:desktop": "boolean",
"notifications:sounds": "object",
"appearance:emoji": "string",
"appearance:ligatures": "boolean",
"appearance:theme:base": "string",
"appearance:theme:custom": "object",
};*/
/**
* Manages user settings.
*/
export default class Settings implements Store, Persistent<ISettings> {
private data: ObservableMap<string, unknown>;
/**
* Construct new Layout store.
*/
constructor() {
this.data = new ObservableMap();
makeAutoObservable(this);
}
get id() {
return "layout";
}
toJSON() {
return JSON.parse(JSON.stringify(mapToRecord(this.data)));
}
@action hydrate(data: ISettings) {
Object.keys(data).forEach((key) =>
this.data.set(key, (data as any)[key]),
);
}
@action set<T extends keyof ISettings>(key: T, value: ISettings[T]) {
return this.data.set(key, value);
}
@computed get<T extends keyof ISettings>(key: T) {
return this.data.get(key) as ISettings[T] | undefined;
}
@action setUnchecked(key: string, value: unknown) {
return this.data.set(key, value);
}
@computed getUnchecked(key: string) {
return this.data.get(key);
}
}