-
-
-
-
-
-

- selected !== "light" && setTheme({ base: "light" })
- }
- onContextMenu={(e) => e.preventDefault()}
- />
-
-
-
-
-
-

- selected !== "dark" && setTheme({ base: "dark" })
- }
- onContextMenu={(e) => e.preventDefault()}
- />
-
-
-
-
-
+
+
+
- {isExperimentEnabled("theme_shop") && (
-
-
}
- action="chevron"
- description={"Browse themes made by the community"}
- hover>
-
-
-
- )}
+
+
+
-
-
-
-
-
- {/* TOFIX: Chane this checkbox to turn off the seasonal home page animations*/}
-
- setTheme({
- ligatures: !props.settings.theme?.ligatures,
- })
- }
- description={
- "Displays effects in the home tab during holiday seasons."
- }>
- Seasonal theme
-
-
- {/*
-
-
-
-
- }
- checked
- >
-
-
-
- }
- disabled
- >
-
-
-
*/}
-
-
- {/*
}
- description={"Customize the look of your app using themes."}
- action="chevron">
- Themes
-
-
}
- description={"Change the font and size used in the app."}
- action="chevron">
- {`Font & text size`}
-
-
}
- description={"Change the look of your messages."}
- action="chevron">
- Message Display
-
-
}
- description={"Personalize your client with an emoji pack."}
- action="chevron">
- Emoji Packs
-
-
Advanced
-
}
- description={"Customize the client CSS to your heart's content"}
- action="chevron">
- Custom CSS
- */}
-
-
-
-
- pushOverride({ font: e.currentTarget.value as Fonts })
- }>
- {FONT_KEYS.map((key) => (
-
- ))}
-
- {/* TOFIX: Only show when a font with ligature support is selected, i.e.: Inter.*/}
-
- setTheme({
- ligatures: !props.settings.theme?.ligatures,
- })
- }
- description={
-
- }>
-
-
-
-
-
-
-
-
-
-
-
setEmojiPack("mutant")}
- data-active={emojiPack === "mutant"}>
-

e.preventDefault()}
- />
-
-
-
-
-
setEmojiPack("twemoji")}
- data-active={emojiPack === "twemoji"}>
-

e.preventDefault()}
- />
-
-
Twemoji
-
-
-
-
-
setEmojiPack("openmoji")}
- data-active={emojiPack === "openmoji"}>
-

e.preventDefault()}
- />
-
-
Openmoji
-
-
-
setEmojiPack("noto")}
- data-active={emojiPack === "noto"}>
-

e.preventDefault()}
- />
-
-
Noto Emoji
-
-
-
-
}>
-
-
- }>
-
-
-
writeClipboard(JSON.stringify(theme))}>
- }>
- {" "}
- {/*TOFIX: Try to put the tooltip above the .code div without messing up the css challenge */}
- {JSON.stringify(theme)}
-
-
-
- }>
-
-
-
+
+
App
-
- {(
- [
- "accent",
- "background",
- "foreground",
- "primary-background",
- "primary-header",
- "secondary-background",
- "secondary-foreground",
- "secondary-header",
- "tertiary-background",
- "tertiary-foreground",
- "block",
- "message-box",
- "mention",
- "scrollbar-thumb",
- "scrollbar-track",
- "status-online",
- "status-away",
- "status-busy",
- "status-streaming",
- "status-invisible",
- "success",
- "warning",
- "error",
- "hover",
- ] as const
- ).map((x) => (
-
-
-
- setOverride({
- [x]: v.currentTarget.value,
- })
- }
- />
-
-
- {x}
-
-
-
- e.currentTarget.parentElement?.parentElement
- ?.querySelector("input")
- ?.click()
- }>
-
-
-
- setOverride({
- [x]: y.currentTarget.value,
- })
- }
- />
-
-
- ))}
-
+
}>
-
-
-
-
- pushOverride({
- monospaceFont: e.currentTarget
- .value as MonospaceFonts,
- })
- }>
- {MONOSPACE_FONT_KEYS.map((key) => (
-
- ))}
-
-
-
-
-
-
setCSS(ev.currentTarget.value)}
- />
+
+
);
-}
-
-export const Appearance = connectState(Component, (state) => {
- return {
- settings: state.settings,
- };
});
-function getContrastingColour(hex: string, fallback: string): string {
- hex = hex.replace("#", "");
- const r = parseInt(hex.substr(0, 2), 16);
- const g = parseInt(hex.substr(2, 2), 16);
- const b = parseInt(hex.substr(4, 2), 16);
- const cc = (r * 299 + g * 587 + b * 114) / 1000;
- if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(cc))
- return getContrastingColour(fallback, "#fffff");
- return cc >= 175 ? "black" : "white";
-}
+// (
undefined,
);
@@ -244,7 +244,3 @@ function changeAudioDevice(deviceId: string, deviceType: string) {
window.localStorage.setItem("audioOutputDevice", deviceId);
}
}
-
-export const Audio = connectState(Component, () => {
- return;
-});
diff --git a/src/pages/settings/panes/Experiments.tsx b/src/pages/settings/panes/Experiments.tsx
index 7e50c892..b91e756f 100644
--- a/src/pages/settings/panes/Experiments.tsx
+++ b/src/pages/settings/panes/Experiments.tsx
@@ -1,22 +1,19 @@
+import { observer } from "mobx-react-lite";
+
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
-import { dispatch } from "../../../redux";
-import { connectState } from "../../../redux/connector";
+import { useApplicationState } from "../../../mobx/State";
import {
AVAILABLE_EXPERIMENTS,
- ExperimentOptions,
EXPERIMENTS,
- isExperimentEnabled,
-} from "../../../redux/reducers/experiments";
+} from "../../../mobx/stores/Experiments";
import Checkbox from "../../../components/ui/Checkbox";
-interface Props {
- options?: ExperimentOptions;
-}
+export const ExperimentsPage = observer(() => {
+ const experiments = useApplicationState().experiments;
-export function Component(props: Props) {
return (
@@ -25,15 +22,8 @@ export function Component(props: Props) {
{AVAILABLE_EXPERIMENTS.map((key) => (
- dispatch({
- type: enabled
- ? "EXPERIMENTS_ENABLE"
- : "EXPERIMENTS_DISABLE",
- key,
- })
- }
+ checked={experiments.isEnabled(key)}
+ onChange={(enabled) => experiments.setEnabled(key, enabled)}
description={EXPERIMENTS[key].description}>
{EXPERIMENTS[key].title}
@@ -45,10 +35,4 @@ export function Component(props: Props) {
)}
);
-}
-
-export const ExperimentsPage = connectState(Component, (state) => {
- return {
- options: state.experiments,
- };
});
diff --git a/src/pages/settings/panes/Languages.tsx b/src/pages/settings/panes/Languages.tsx
index 873b4611..6947bb12 100644
--- a/src/pages/settings/panes/Languages.tsx
+++ b/src/pages/settings/panes/Languages.tsx
@@ -1,8 +1,10 @@
+import { observer } from "mobx-react-lite";
+
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
+import { useMemo } from "preact/hooks";
-import { dispatch } from "../../../redux";
-import { connectState } from "../../../redux/connector";
+import { useApplicationState } from "../../../mobx/State";
import {
Language,
@@ -17,26 +19,25 @@ import enchantingTableWEBP from "../assets/enchanting_table.webp";
import tamilFlagPNG from "../assets/tamil_nadu_flag.png";
import tokiponaSVG from "../assets/toki_pona.svg";
-type Props = {
- locale: Language;
-};
+type Key = [Language, LanguageEntry];
-type Key = [string, LanguageEntry];
+interface Props {
+ entry: Key;
+ selected: boolean;
+ onSelect: () => void;
+}
-function Entry({ entry: [x, lang], locale }: { entry: Key } & Props) {
+/**
+ * Component providing individual language entries.
+ * @param param0 Entry data
+ */
+function Entry({ entry: [x, lang], selected, onSelect }: Props) {
return (
{
- if (v) {
- dispatch({
- type: "SET_LOCALE",
- locale: x as Language,
- });
- }
- }}>
+ checked={selected}
+ onChange={onSelect}>
{lang.i18n === "ta" ? (
![]()
[
- x,
- Langs[x as keyof typeof Langs],
- ]) as Key[];
+/**
+ * Component providing the language selection menu.
+ */
+export const Languages = observer(() => {
+ const locale = useApplicationState().locale;
+ const language = locale.getLanguage();
- // Get the user's system language. Check for exact
- // matches first, otherwise check for partial matches
- const preferredLanguage =
- navigator.languages.filter((lang) =>
- languages.find((l) => l[0].replace(/_/g, "-") == lang),
- )?.[0] ||
- navigator.languages
- ?.map((x) => x.split("-")[0])
- ?.filter((lang) => languages.find((l) => l[0] == lang))?.[0]
- ?.split("-")[0];
+ // Generate languages array.
+ const languages = useMemo(() => {
+ const languages = Object.keys(Langs).map((x) => [
+ x,
+ Langs[x as keyof typeof Langs],
+ ]) as Key[];
- if (preferredLanguage) {
- // This moves the user's system language to the top of the language list
- const prefLangKey = languages.find(
- (lang) => lang[0].replace(/_/g, "-") == preferredLanguage,
- );
- if (prefLangKey) {
- languages.splice(
- 0,
- 0,
- languages.splice(languages.indexOf(prefLangKey), 1)[0],
+ // Get the user's system language. Check for exact
+ // matches first, otherwise check for partial matches
+ const preferredLanguage =
+ navigator.languages.filter((lang) =>
+ languages.find((l) => l[0].replace(/_/g, "-") == lang),
+ )?.[0] ||
+ navigator.languages
+ ?.map((x) => x.split("-")[0])
+ ?.filter((lang) => languages.find((l) => l[0] == lang))?.[0]
+ ?.split("-")[0];
+
+ if (preferredLanguage) {
+ // This moves the user's system language to the top of the language list
+ const prefLangKey = languages.find(
+ (lang) => lang[0].replace(/_/g, "-") == preferredLanguage,
);
+
+ if (prefLangKey) {
+ languages.splice(
+ 0,
+ 0,
+ languages.splice(languages.indexOf(prefLangKey), 1)[0],
+ );
+ }
}
- }
+
+ return languages;
+ }, []);
+
+ // Creates entries with given key.
+ const EntryFactory = ([x, lang]: Key) => (
+
locale.setLanguage(x)}
+ />
+ );
return (
@@ -98,11 +121,7 @@ export function Component(props: Props) {
- {languages
- .filter(([, lang]) => !lang.cat)
- .map(([x, lang]) => (
-
- ))}
+ {languages.filter(([, lang]) => !lang.cat).map(EntryFactory)}
@@ -110,9 +129,7 @@ export function Component(props: Props) {
{languages
.filter(([, lang]) => lang.cat === "const")
- .map(([x, lang]) => (
-
- ))}
+ .map(EntryFactory)}
@@ -120,9 +137,7 @@ export function Component(props: Props) {
{languages
.filter(([, lang]) => lang.cat === "alt")
- .map(([x, lang]) => (
-
- ))}
+ .map(EntryFactory)}
@@ -137,10 +152,4 @@ export function Component(props: Props) {
);
-}
-
-export const Languages = connectState(Component, (state) => {
- return {
- locale: state.locale,
- };
});
diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx
index e1fc88f2..6c692c44 100644
--- a/src/pages/settings/panes/Notifications.tsx
+++ b/src/pages/settings/panes/Notifications.tsx
@@ -1,4 +1,4 @@
-import defaultsDeep from "lodash.defaultsdeep";
+import { observer } from "mobx-react-lite";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
@@ -6,28 +6,17 @@ import { useContext, useEffect, useState } from "preact/hooks";
import { urlBase64ToUint8Array } from "../../../lib/conversion";
-import { dispatch } from "../../../redux";
-import { connectState } from "../../../redux/connector";
-import {
- DEFAULT_SOUNDS,
- NotificationOptions,
- SoundOptions,
-} from "../../../redux/reducers/settings";
+import { useApplicationState } from "../../../mobx/State";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import Checkbox from "../../../components/ui/Checkbox";
-import { SOUNDS_ARRAY } from "../../../assets/sounds/Audio";
-
-interface Props {
- options?: NotificationOptions;
-}
-
-export function Component({ options }: Props) {
+export const Notifications = observer(() => {
const client = useContext(AppContext);
const { openScreen } = useIntermediate();
+ const settings = useApplicationState().settings;
const [pushEnabled, setPushEnabled] = useState(
undefined,
);
@@ -42,10 +31,6 @@ export function Component({ options }: Props) {
});
}, []);
- const enabledSounds: SoundOptions = defaultsDeep(
- options?.sounds ?? {},
- DEFAULT_SOUNDS,
- );
return (
@@ -53,7 +38,7 @@ export function Component({ options }: Props) {
}
@@ -61,6 +46,7 @@ export function Component({ options }: Props) {
if (desktopEnabled) {
const permission =
await Notification.requestPermission();
+
if (permission !== "granted") {
return openScreen({
id: "error",
@@ -69,10 +55,7 @@ export function Component({ options }: Props) {
}
}
- dispatch({
- type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
- options: { desktopEnabled },
- });
+ settings.set("notifications:desktop", desktopEnabled);
}}>
@@ -125,32 +108,16 @@ export function Component({ options }: Props) {
- {SOUNDS_ARRAY.map((key) => (
+ {settings.sounds.getState().map(({ id, enabled }) => (
- dispatch({
- type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
- options: {
- sounds: {
- ...options?.sounds,
- [key]: enabled,
- },
- },
- })
+ settings.sounds.setEnabled(id, enabled)
}>
-
+
))}
);
-}
-
-export const Notifications = connectState(Component, (state) => {
- return {
- options: state.settings.notification,
- };
});
diff --git a/src/pages/settings/panes/Panes.module.scss b/src/pages/settings/panes/Panes.module.scss
index 9c8b70fa..d7037865 100644
--- a/src/pages/settings/panes/Panes.module.scss
+++ b/src/pages/settings/panes/Panes.module.scss
@@ -461,97 +461,6 @@
display: flex;
flex-direction: column;
}
-
- .actions {
- gap: 8px;
- display: flex;
- margin: 18px 0 8px 0;
-
- .code {
- cursor: pointer;
- display: flex;
- align-items: center;
- font-size: 0.875rem;
- min-width: 0;
- flex-grow: 1;
- padding: 8px;
- font-family: var(--monospace-font);
- border-radius: var(--border-radius);
- background: var(--secondary-background);
-
- > div {
- width: 100%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- }
- }
-
- .overrides {
- row-gap: 8px;
- display: grid;
- column-gap: 16px;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- margin-bottom: 20px;
-
- .entry {
- padding: 12px;
- margin-top: 8px;
- border: 1px solid black;
- border-radius: var(--border-radius);
-
- span {
- flex: 1;
- display: block;
- font-weight: 600;
- font-size: 0.875rem;
- margin-bottom: 8px;
- text-transform: capitalize;
-
- background: inherit;
- background-clip: text;
- -webkit-background-clip: text;
- }
-
- .override {
- gap: 8px;
- display: flex;
-
- .picker {
- width: 38px;
- height: 38px;
- display: grid;
- cursor: pointer;
- place-items: center;
- border-radius: var(--border-radius);
- background: var(--primary-background);
- }
-
- input[type="text"] {
- width: 0;
- min-width: 0;
- flex-grow: 1;
- }
- }
-
- .input {
- width: 0;
- height: 0;
- position: relative;
-
- input {
- opacity: 0;
- border: none;
- display: block;
- cursor: pointer;
- position: relative;
-
- top: 48px;
- }
- }
- }
- }
}
.sessions {
diff --git a/src/pages/settings/panes/Sync.tsx b/src/pages/settings/panes/Sync.tsx
index 9a5b43d6..f426c4d4 100644
--- a/src/pages/settings/panes/Sync.tsx
+++ b/src/pages/settings/panes/Sync.tsx
@@ -1,17 +1,16 @@
+import { observer } from "mobx-react-lite";
+
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
-import { dispatch } from "../../../redux";
-import { connectState } from "../../../redux/connector";
-import { SyncKeys, SyncOptions } from "../../../redux/reducers/sync";
+import { useApplicationState } from "../../../mobx/State";
+import { SyncKeys } from "../../../mobx/stores/Sync";
import Checkbox from "../../../components/ui/Checkbox";
-interface Props {
- options?: SyncOptions;
-}
+export const Sync = observer(() => {
+ const sync = useApplicationState().sync;
-export function Component(props: Props) {
return (
{/*
@@ -31,22 +30,13 @@ export function Component(props: Props) {
).map(([key, title]) => (
}
- onChange={(enabled) =>
- dispatch({
- type: enabled
- ? "SYNC_ENABLE_KEY"
- : "SYNC_DISABLE_KEY",
- key,
- })
- }>
+ onChange={() => sync.toggle(key)}>
))}
@@ -55,10 +45,4 @@ export function Component(props: Props) {
*/}
);
-}
-
-export const Sync = connectState(Component, (state) => {
- return {
- options: state.sync,
- };
});
diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx
index 33a337b2..2c87be4c 100644
--- a/src/pages/settings/panes/ThemeShop.tsx
+++ b/src/pages/settings/panes/ThemeShop.tsx
@@ -1,19 +1,11 @@
-import { Plus, Check } from "@styled-icons/boxicons-regular";
-import {
- Star,
- BarChartAlt2,
- Brush,
- Bookmark,
-} from "@styled-icons/boxicons-solid";
import styled from "styled-components";
import { useEffect, useState } from "preact/hooks";
-import { dispatch } from "../../../redux";
+import { useApplicationState } from "../../../mobx/State";
-import { Theme, generateVariables, ThemeOptions } from "../../../context/Theme";
+import { Theme, generateVariables } from "../../../context/Theme";
-import InputBox from "../../../components/ui/InputBox";
import Tip from "../../../components/ui/Tip";
import previewPath from "../assets/preview.svg";
@@ -258,6 +250,8 @@ export function ThemeShop() {
>(null);
const [themeData, setThemeData] = useState>({});
+ const themes = useApplicationState().settings.theme;
+
async function fetchThemeList() {
const manifest = await fetchManifest();
setThemeList(
@@ -352,21 +346,9 @@ export function ThemeShop() {
data-loaded={Reflect.has(themeData, slug)}>