mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-27 07:52:11 -05:00
feat(mobx): start implementing theme store
This commit is contained in:
parent
26a34032f9
commit
bd4369cf29
9 changed files with 207 additions and 112 deletions
|
@ -1,11 +1,11 @@
|
||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import { Download, CloudDownload } from "@styled-icons/boxicons-regular";
|
import { Download, CloudDownload } from "@styled-icons/boxicons-regular";
|
||||||
|
|
||||||
import { useContext, useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
import { internalSubscribe } from "../../lib/eventEmitter";
|
import { internalSubscribe } from "../../lib/eventEmitter";
|
||||||
|
|
||||||
import { ThemeContext } from "../../context/Theme";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
import IconButton from "../ui/IconButton";
|
import IconButton from "../ui/IconButton";
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export default function UpdateIndicator({ style }: Props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!pending) return null;
|
if (!pending) return null;
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useApplicationState().settings.theme;
|
||||||
|
|
||||||
if (style === "titlebar") {
|
if (style === "titlebar") {
|
||||||
return (
|
return (
|
||||||
|
@ -36,7 +36,10 @@ export default function UpdateIndicator({ style }: Props) {
|
||||||
content="A new update is available!"
|
content="A new update is available!"
|
||||||
placement="bottom">
|
placement="bottom">
|
||||||
<div onClick={() => updateSW(true)}>
|
<div onClick={() => updateSW(true)}>
|
||||||
<CloudDownload size={22} color={theme.success} />
|
<CloudDownload
|
||||||
|
size={22}
|
||||||
|
color={theme.getVariable("success")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +50,7 @@ export default function UpdateIndicator({ style }: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton onClick={() => updateSW(true)}>
|
<IconButton onClick={() => updateSW(true)}>
|
||||||
<Download size={22} color={theme.success} />
|
<Download size={22} color={theme.getVariable("success")} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,10 @@ import { useParams } from "react-router-dom";
|
||||||
import { Masquerade } from "revolt-api/types/Channels";
|
import { Masquerade } from "revolt-api/types/Channels";
|
||||||
import { Presence } from "revolt-api/types/Users";
|
import { Presence } from "revolt-api/types/Users";
|
||||||
import { User } from "revolt.js/dist/maps/Users";
|
import { User } from "revolt.js/dist/maps/Users";
|
||||||
import { Nullable } from "revolt.js/dist/util/null";
|
|
||||||
import styled, { css } from "styled-components";
|
import styled, { css } from "styled-components";
|
||||||
|
|
||||||
import { useContext } from "preact/hooks";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { ThemeContext } from "../../../context/Theme";
|
|
||||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import fallback from "../assets/user.png";
|
import fallback from "../assets/user.png";
|
||||||
|
@ -26,15 +24,15 @@ interface Props extends IconBaseProps<User> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useStatusColour(user?: User) {
|
export function useStatusColour(user?: User) {
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useApplicationState().settings.theme;
|
||||||
|
|
||||||
return user?.online && user?.status?.presence !== Presence.Invisible
|
return user?.online && user?.status?.presence !== Presence.Invisible
|
||||||
? user?.status?.presence === Presence.Idle
|
? user?.status?.presence === Presence.Idle
|
||||||
? theme["status-away"]
|
? theme.getVariable("status-away")
|
||||||
: user?.status?.presence === Presence.Busy
|
: user?.status?.presence === Presence.Busy
|
||||||
? theme["status-busy"]
|
? theme.getVariable("status-busy")
|
||||||
: theme["status-online"]
|
: theme.getVariable("status-online")
|
||||||
: theme["status-invisible"];
|
: theme.getVariable("status-invisible");
|
||||||
}
|
}
|
||||||
|
|
||||||
const VoiceIndicator = styled.div<{ status: VoiceStatus }>`
|
const VoiceIndicator = styled.div<{ status: VoiceStatus }>`
|
||||||
|
|
|
@ -6,9 +6,6 @@ import { useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { useApplicationState } from "../mobx/State";
|
import { useApplicationState } from "../mobx/State";
|
||||||
import { getState } from "../redux";
|
import { getState } from "../redux";
|
||||||
import { connectState } from "../redux/connector";
|
|
||||||
|
|
||||||
import { Children } from "../types/Preact";
|
|
||||||
|
|
||||||
export type Variables =
|
export type Variables =
|
||||||
| "accent"
|
| "accent"
|
||||||
|
@ -66,9 +63,11 @@ export type MonospaceFonts =
|
||||||
| "Ubuntu Mono"
|
| "Ubuntu Mono"
|
||||||
| "JetBrains Mono";
|
| "JetBrains Mono";
|
||||||
|
|
||||||
export type Theme = {
|
export type Overrides = {
|
||||||
[variable in Variables]: string;
|
[variable in Variables]: string;
|
||||||
} & {
|
};
|
||||||
|
|
||||||
|
export type Theme = Overrides & {
|
||||||
light?: boolean;
|
light?: boolean;
|
||||||
font?: Fonts;
|
font?: Fonts;
|
||||||
css?: string;
|
css?: string;
|
||||||
|
@ -228,7 +227,6 @@ export const DEFAULT_MONO_FONT = "Fira Code";
|
||||||
// Generated from https://gitlab.insrt.uk/revolt/community/themes
|
// Generated from https://gitlab.insrt.uk/revolt/community/themes
|
||||||
export const PRESETS: Record<string, Theme> = {
|
export const PRESETS: Record<string, Theme> = {
|
||||||
light: {
|
light: {
|
||||||
light: true,
|
|
||||||
accent: "#FD6671",
|
accent: "#FD6671",
|
||||||
background: "#F6F6F6",
|
background: "#F6F6F6",
|
||||||
foreground: "#000000",
|
foreground: "#000000",
|
||||||
|
@ -255,7 +253,6 @@ export const PRESETS: Record<string, Theme> = {
|
||||||
"status-invisible": "#A5A5A5",
|
"status-invisible": "#A5A5A5",
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
light: false,
|
|
||||||
accent: "#FD6671",
|
accent: "#FD6671",
|
||||||
background: "#191919",
|
background: "#191919",
|
||||||
foreground: "#F6F6F6",
|
foreground: "#F6F6F6",
|
||||||
|
@ -319,33 +316,22 @@ export const generateVariables = (theme: Theme) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load the default default them and apply extras later
|
export default function Theme() {
|
||||||
export const ThemeContext = createContext<Theme>(PRESETS["dark"]);
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
children: Children;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Theme({ children }: Props) {
|
|
||||||
const settings = useApplicationState().settings;
|
const settings = useApplicationState().settings;
|
||||||
|
const theme = settings.theme;
|
||||||
const theme: Theme = {
|
|
||||||
...getBaseTheme(settings.get("appearance:theme:base") ?? "dark"),
|
|
||||||
...settings.get("appearance:theme:custom"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const root = document.documentElement.style;
|
const root = document.documentElement.style;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const font = theme.font ?? DEFAULT_FONT;
|
const font = theme.getFont() ?? DEFAULT_FONT;
|
||||||
root.setProperty("--font", `"${font}"`);
|
root.setProperty("--font", `"${font}"`);
|
||||||
FONTS[font].load();
|
FONTS[font].load();
|
||||||
}, [root, theme.font]);
|
}, [root, theme.getFont()]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const font = theme.monospaceFont ?? DEFAULT_MONO_FONT;
|
const font = theme.getMonospaceFont() ?? DEFAULT_MONO_FONT;
|
||||||
root.setProperty("--monospace-font", `"${font}"`);
|
root.setProperty("--monospace-font", `"${font}"`);
|
||||||
MONOSPACE_FONTS[font].load();
|
MONOSPACE_FONTS[font].load();
|
||||||
}, [root, theme.monospaceFont]);
|
}, [root, theme.getMonospaceFont()]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
root.setProperty(
|
root.setProperty(
|
||||||
|
@ -363,16 +349,14 @@ export default function Theme({ children }: Props) {
|
||||||
return () => window.removeEventListener("resize", resize);
|
return () => window.removeEventListener("resize", resize);
|
||||||
}, [root]);
|
}, [root]);
|
||||||
|
|
||||||
|
const variables = theme.getVariables();
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<meta name="theme-color" content={theme["background"]} />
|
<meta name="theme-color" content={variables["background"]} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<GlobalTheme theme={theme} />
|
<GlobalTheme theme={variables} />
|
||||||
{theme.css && (
|
<style dangerouslySetInnerHTML={{ __html: theme.getCSS() ?? "" }} />
|
||||||
<style dangerouslySetInnerHTML={{ __html: theme.css }} />
|
</>
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,14 @@ export default function Context({ children }: { children: Children }) {
|
||||||
return (
|
return (
|
||||||
<Router basename={import.meta.env.BASE_URL}>
|
<Router basename={import.meta.env.BASE_URL}>
|
||||||
<State>
|
<State>
|
||||||
<Theme>
|
<Settings>
|
||||||
<Settings>
|
<Locale>
|
||||||
<Locale>
|
<Intermediate>
|
||||||
<Intermediate>
|
<Client>{children}</Client>
|
||||||
<Client>{children}</Client>
|
</Intermediate>
|
||||||
</Intermediate>
|
</Locale>
|
||||||
</Locale>
|
</Settings>
|
||||||
</Settings>
|
<Theme />
|
||||||
</Theme>
|
|
||||||
</State>
|
</State>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,11 +2,12 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
|
||||||
|
|
||||||
import { mapToRecord } from "../../lib/conversion";
|
import { mapToRecord } from "../../lib/conversion";
|
||||||
|
|
||||||
import { Theme } from "../../context/Theme";
|
import { Fonts, MonospaceFonts, Overrides, Theme } from "../../context/Theme";
|
||||||
|
|
||||||
import { Sounds } from "../../assets/sounds/Audio";
|
import { Sounds } from "../../assets/sounds/Audio";
|
||||||
import Persistent from "../interfaces/Persistent";
|
import Persistent from "../interfaces/Persistent";
|
||||||
import Store from "../interfaces/Store";
|
import Store from "../interfaces/Store";
|
||||||
|
import STheme from "./helpers/STheme";
|
||||||
|
|
||||||
export type SoundOptions = {
|
export type SoundOptions = {
|
||||||
[key in Sounds]?: boolean;
|
[key in Sounds]?: boolean;
|
||||||
|
@ -20,43 +21,35 @@ interface ISettings {
|
||||||
|
|
||||||
"appearance:emoji": EmojiPack;
|
"appearance:emoji": EmojiPack;
|
||||||
"appearance:ligatures": boolean;
|
"appearance:ligatures": boolean;
|
||||||
"appearance:theme:base": string;
|
|
||||||
"appearance:theme:custom": Partial<Theme>;
|
"appearance:theme:base": "dark" | "light";
|
||||||
|
"appearance:theme:overrides": Partial<Overrides>;
|
||||||
|
"appearance:theme:light": boolean;
|
||||||
|
"appearance:theme:font": Fonts;
|
||||||
|
"appearance:theme:monoFont": MonospaceFonts;
|
||||||
|
"appearance:theme:css": string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*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.
|
* Manages user settings.
|
||||||
*/
|
*/
|
||||||
export default class Settings implements Store, Persistent<ISettings> {
|
export default class Settings implements Store, Persistent<ISettings> {
|
||||||
private data: ObservableMap<string, unknown>;
|
private data: ObservableMap<string, unknown>;
|
||||||
|
|
||||||
|
theme: STheme;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct new Layout store.
|
* Construct new Settings store.
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.data = new ObservableMap();
|
this.data = new ObservableMap();
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
|
|
||||||
|
this.theme = new STheme(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return "layout";
|
return "settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
|
@ -69,18 +62,38 @@ export default class Settings implements Store, Persistent<ISettings> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a settings key.
|
||||||
|
* @param key Colon-divided key
|
||||||
|
* @param value Value
|
||||||
|
*/
|
||||||
@action set<T extends keyof ISettings>(key: T, value: ISettings[T]) {
|
@action set<T extends keyof ISettings>(key: T, value: ISettings[T]) {
|
||||||
return this.data.set(key, value);
|
this.data.set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a settings key.
|
||||||
|
* @param key Colon-divided key
|
||||||
|
* @returns Value at key
|
||||||
|
*/
|
||||||
@computed get<T extends keyof ISettings>(key: T) {
|
@computed get<T extends keyof ISettings>(key: T) {
|
||||||
return this.data.get(key) as ISettings[T] | undefined;
|
return this.data.get(key) as ISettings[T] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value in settings without type-checking.
|
||||||
|
* @param key Colon-divided key
|
||||||
|
* @param value Value
|
||||||
|
*/
|
||||||
@action setUnchecked(key: string, value: unknown) {
|
@action setUnchecked(key: string, value: unknown) {
|
||||||
return this.data.set(key, value);
|
this.data.set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a settings key with unknown type.
|
||||||
|
* @param key Colon-divided key
|
||||||
|
* @returns Value at key
|
||||||
|
*/
|
||||||
@computed getUnchecked(key: string) {
|
@computed getUnchecked(key: string) {
|
||||||
return this.data.get(key);
|
return this.data.get(key);
|
||||||
}
|
}
|
||||||
|
|
94
src/mobx/stores/helpers/STheme.ts
Normal file
94
src/mobx/stores/helpers/STheme.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import { makeAutoObservable, computed } from "mobx";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Theme,
|
||||||
|
PRESETS,
|
||||||
|
Variables,
|
||||||
|
DEFAULT_FONT,
|
||||||
|
DEFAULT_MONO_FONT,
|
||||||
|
} from "../../../context/Theme";
|
||||||
|
|
||||||
|
import Settings from "../Settings";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for reading and writing themes.
|
||||||
|
*/
|
||||||
|
export default class STheme {
|
||||||
|
private settings: Settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new theme helper.
|
||||||
|
* @param settings Settings parent class
|
||||||
|
*/
|
||||||
|
constructor(settings: Settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the base theme used for this theme.
|
||||||
|
* @returns Id of base theme
|
||||||
|
*/
|
||||||
|
@computed getBase() {
|
||||||
|
return this.settings.get("appearance:theme:base") ?? "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the theme is light.
|
||||||
|
* @returns True if the theme is light
|
||||||
|
*/
|
||||||
|
@computed isLight() {
|
||||||
|
return (
|
||||||
|
this.settings.get("appearance:theme:light") ??
|
||||||
|
this.getBase() === "light"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current theme's CSS variables.
|
||||||
|
* @returns Record of CSS variables
|
||||||
|
*/
|
||||||
|
@computed getVariables(): Theme {
|
||||||
|
return {
|
||||||
|
...PRESETS[this.getBase()],
|
||||||
|
...this.settings.get("appearance:theme:overrides"),
|
||||||
|
light: this.isLight(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific value of a variable by its key.
|
||||||
|
* @param key Variable
|
||||||
|
* @returns Value of variable
|
||||||
|
*/
|
||||||
|
@computed getVariable(key: Variables) {
|
||||||
|
return (this.settings.get("appearance:theme:overrides") ??
|
||||||
|
PRESETS[this.getBase()])[key]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current applied font.
|
||||||
|
* @returns Current font
|
||||||
|
*/
|
||||||
|
@computed getFont() {
|
||||||
|
return this.settings.get("appearance:theme:font") ?? DEFAULT_FONT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current applied monospace font.
|
||||||
|
* @returns Current monospace font
|
||||||
|
*/
|
||||||
|
@computed getMonospaceFont() {
|
||||||
|
return (
|
||||||
|
this.settings.get("appearance:theme:monoFont") ?? DEFAULT_MONO_FONT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently applied CSS snippet.
|
||||||
|
* @returns CSS string
|
||||||
|
*/
|
||||||
|
@computed getCSS() {
|
||||||
|
return this.settings.get("appearance:theme:css");
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,13 +5,9 @@ import { LIBRARY_VERSION } from "revolt.js";
|
||||||
|
|
||||||
import styles from "./Login.module.scss";
|
import styles from "./Login.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useContext } from "preact/hooks";
|
|
||||||
|
|
||||||
import { useApplicationState } from "../../mobx/State";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
import { ThemeContext } from "../../context/Theme";
|
|
||||||
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
|
||||||
|
|
||||||
import LocaleSelector from "../../components/common/LocaleSelector";
|
import LocaleSelector from "../../components/common/LocaleSelector";
|
||||||
import background from "./background.jpg";
|
import background from "./background.jpg";
|
||||||
|
|
||||||
|
@ -23,8 +19,9 @@ import { FormReset, FormSendReset } from "./forms/FormReset";
|
||||||
import { FormResend, FormVerify } from "./forms/FormVerify";
|
import { FormResend, FormVerify } from "./forms/FormVerify";
|
||||||
|
|
||||||
export default observer(() => {
|
export default observer(() => {
|
||||||
const theme = useContext(ThemeContext);
|
const state = useApplicationState();
|
||||||
const configuration = useApplicationState().config.get();
|
const theme = state.settings.theme;
|
||||||
|
const configuration = state.config.get();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -33,7 +30,10 @@ export default observer(() => {
|
||||||
)}
|
)}
|
||||||
<div className={styles.login}>
|
<div className={styles.login}>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<meta name="theme-color" content={theme.background} />
|
<meta
|
||||||
|
name="theme-color"
|
||||||
|
content={theme.getVariable("background")}
|
||||||
|
/>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.attribution}>
|
<div className={styles.attribution}>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
|
|
||||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||||
|
|
||||||
import { ThemeContext } from "../../context/Theme";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
import Category from "../../components/ui/Category";
|
import Category from "../../components/ui/Category";
|
||||||
import Header from "../../components/ui/Header";
|
import Header from "../../components/ui/Header";
|
||||||
|
@ -53,7 +53,7 @@ export function GenericSettings({
|
||||||
showExitButton,
|
showExitButton,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useApplicationState().settings.theme;
|
||||||
const { page } = useParams<{ page: string }>();
|
const { page } = useParams<{ page: string }>();
|
||||||
|
|
||||||
const [closing, setClosing] = useState(false);
|
const [closing, setClosing] = useState(false);
|
||||||
|
@ -94,8 +94,8 @@ export function GenericSettings({
|
||||||
name="theme-color"
|
name="theme-color"
|
||||||
content={
|
content={
|
||||||
isTouchscreenDevice
|
isTouchscreenDevice
|
||||||
? theme["background"]
|
? theme.getVariable("background")
|
||||||
: theme["secondary-background"]
|
: theme.getVariable("secondary-background")
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Reset, Import } from "@styled-icons/boxicons-regular";
|
import { Reset, Import } from "@styled-icons/boxicons-regular";
|
||||||
import { Pencil, Store } from "@styled-icons/boxicons-solid";
|
import { Pencil, Store } from "@styled-icons/boxicons-solid";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
// @ts-expect-error shade-blend-color does not have typings.
|
// @ts-expect-error shade-blend-color does not have typings.
|
||||||
import pSBC from "shade-blend-color";
|
import pSBC from "shade-blend-color";
|
||||||
|
|
||||||
|
@ -8,16 +9,14 @@ import { Text } from "preact-i18n";
|
||||||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||||
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
|
||||||
|
|
||||||
|
|
||||||
import { debounce } from "../../../lib/debounce";
|
import { debounce } from "../../../lib/debounce";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { dispatch } from "../../../redux";
|
import { dispatch } from "../../../redux";
|
||||||
import { connectState } from "../../../redux/connector";
|
import { connectState } from "../../../redux/connector";
|
||||||
|
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
|
||||||
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_FONT,
|
DEFAULT_FONT,
|
||||||
DEFAULT_MONO_FONT,
|
DEFAULT_MONO_FONT,
|
||||||
|
@ -28,7 +27,6 @@ import {
|
||||||
MONOSPACE_FONTS,
|
MONOSPACE_FONTS,
|
||||||
MONOSPACE_FONT_KEYS,
|
MONOSPACE_FONT_KEYS,
|
||||||
Theme,
|
Theme,
|
||||||
ThemeContext,
|
|
||||||
ThemeOptions,
|
ThemeOptions,
|
||||||
} from "../../../context/Theme";
|
} from "../../../context/Theme";
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
|
@ -40,14 +38,13 @@ import Checkbox from "../../../components/ui/Checkbox";
|
||||||
import ColourSwatches from "../../../components/ui/ColourSwatches";
|
import ColourSwatches from "../../../components/ui/ColourSwatches";
|
||||||
import ComboBox from "../../../components/ui/ComboBox";
|
import ComboBox from "../../../components/ui/ComboBox";
|
||||||
import InputBox from "../../../components/ui/InputBox";
|
import InputBox from "../../../components/ui/InputBox";
|
||||||
|
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
||||||
import darkSVG from "../assets/dark.svg";
|
import darkSVG from "../assets/dark.svg";
|
||||||
import lightSVG from "../assets/light.svg";
|
import lightSVG from "../assets/light.svg";
|
||||||
import mutantSVG from "../assets/mutant_emoji.svg";
|
import mutantSVG from "../assets/mutant_emoji.svg";
|
||||||
import notoSVG from "../assets/noto_emoji.svg";
|
import notoSVG from "../assets/noto_emoji.svg";
|
||||||
import openmojiSVG from "../assets/openmoji_emoji.svg";
|
import openmojiSVG from "../assets/openmoji_emoji.svg";
|
||||||
import twemojiSVG from "../assets/twemoji_emoji.svg";
|
import twemojiSVG from "../assets/twemoji_emoji.svg";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
@ -55,7 +52,7 @@ interface Props {
|
||||||
|
|
||||||
// ! FIXME: code needs to be rewritten to fix jittering
|
// ! FIXME: code needs to be rewritten to fix jittering
|
||||||
export function Component(props: Props) {
|
export function Component(props: Props) {
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useApplicationState().settings.theme;
|
||||||
const { writeClipboard, openScreen } = useIntermediate();
|
const { writeClipboard, openScreen } = useIntermediate();
|
||||||
|
|
||||||
function setTheme(theme: ThemeOptions) {
|
function setTheme(theme: ThemeOptions) {
|
||||||
|
@ -112,8 +109,7 @@ export function Component(props: Props) {
|
||||||
draggable={false}
|
draggable={false}
|
||||||
data-active={selected === "light"}
|
data-active={selected === "light"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
selected !== "light" &&
|
selected !== "light" && setTheme({ base: "light" })
|
||||||
setTheme({ base: "light" })
|
|
||||||
}
|
}
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
|
@ -138,16 +134,24 @@ export function Component(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isExperimentEnabled('theme_shop') && <Link to="/settings/theme_shop" replace>
|
{isExperimentEnabled("theme_shop") && (
|
||||||
<CategoryButton icon={<Store size={24} />} action="chevron" hover>
|
<Link to="/settings/theme_shop" replace>
|
||||||
<Text id="app.settings.pages.theme_shop.title" />
|
<CategoryButton
|
||||||
</CategoryButton>
|
icon={<Store size={24} />}
|
||||||
</Link>}
|
action="chevron"
|
||||||
|
hover>
|
||||||
|
<Text id="app.settings.pages.theme_shop.title" />
|
||||||
|
</CategoryButton>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<Text id="app.settings.pages.appearance.accent_selector" />
|
<Text id="app.settings.pages.appearance.accent_selector" />
|
||||||
</h3>
|
</h3>
|
||||||
<ColourSwatches value={theme.accent} onChange={setAccent} />
|
<ColourSwatches
|
||||||
|
value={theme.getVariable("accent")}
|
||||||
|
onChange={setAccent}
|
||||||
|
/>
|
||||||
|
|
||||||
{/*<h3>
|
{/*<h3>
|
||||||
<Text id="app.settings.pages.appearance.message_display" />
|
<Text id="app.settings.pages.appearance.message_display" />
|
||||||
|
@ -175,7 +179,7 @@ export function Component(props: Props) {
|
||||||
<Text id="app.settings.pages.appearance.font" />
|
<Text id="app.settings.pages.appearance.font" />
|
||||||
</h3>
|
</h3>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
value={theme.font ?? DEFAULT_FONT}
|
value={theme.getFont()}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
pushOverride({ font: e.currentTarget.value as Fonts })
|
pushOverride({ font: e.currentTarget.value as Fonts })
|
||||||
}>
|
}>
|
||||||
|
@ -363,11 +367,11 @@ export function Component(props: Props) {
|
||||||
<div
|
<div
|
||||||
className={styles.entry}
|
className={styles.entry}
|
||||||
key={x}
|
key={x}
|
||||||
style={{ backgroundColor: theme[x] }}>
|
style={{ backgroundColor: theme.getVariable(x) }}>
|
||||||
<div className={styles.input}>
|
<div className={styles.input}>
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
value={theme[x]}
|
value={theme.getVariable(x)}
|
||||||
onChange={(v) =>
|
onChange={(v) =>
|
||||||
setOverride({
|
setOverride({
|
||||||
[x]: v.currentTarget.value,
|
[x]: v.currentTarget.value,
|
||||||
|
@ -377,8 +381,8 @@ export function Component(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
style={`color: ${getContrastingColour(
|
style={`color: ${getContrastingColour(
|
||||||
theme[x],
|
theme.getVariable(x),
|
||||||
theme["primary-background"],
|
theme.getVariable("primary-background"),
|
||||||
)}`}>
|
)}`}>
|
||||||
{x}
|
{x}
|
||||||
</span>
|
</span>
|
||||||
|
@ -395,7 +399,7 @@ export function Component(props: Props) {
|
||||||
<InputBox
|
<InputBox
|
||||||
type="text"
|
type="text"
|
||||||
className={styles.text}
|
className={styles.text}
|
||||||
value={theme[x]}
|
value={theme.getVariable(x)}
|
||||||
onChange={(y) =>
|
onChange={(y) =>
|
||||||
setOverride({
|
setOverride({
|
||||||
[x]: y.currentTarget.value,
|
[x]: y.currentTarget.value,
|
||||||
|
@ -416,7 +420,7 @@ export function Component(props: Props) {
|
||||||
<Text id="app.settings.pages.appearance.mono_font" />
|
<Text id="app.settings.pages.appearance.mono_font" />
|
||||||
</h3>
|
</h3>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
value={theme.monospaceFont ?? DEFAULT_MONO_FONT}
|
value={theme.getMonospaceFont()}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
pushOverride({
|
pushOverride({
|
||||||
monospaceFont: e.currentTarget
|
monospaceFont: e.currentTarget
|
||||||
|
|
Loading…
Reference in a new issue