Merge branch 'mobx'

This commit is contained in:
Paul
2021-12-24 11:45:49 +00:00
115 changed files with 3973 additions and 3311 deletions

View File

@@ -1,528 +1,53 @@
import {
Reset,
Import,
FontFamily,
CodeAlt,
} from "@styled-icons/boxicons-regular";
import {
Pencil,
Store,
Palette,
HappyBeaming,
QuoteLeft,
} from "@styled-icons/boxicons-solid";
import { Link } from "react-router-dom";
// @ts-expect-error shade-blend-color does not have typings.
import pSBC from "shade-blend-color";
import { observer } from "mobx-react-lite";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { debounce } from "../../../lib/debounce";
import { dispatch } from "../../../redux";
import { connectState } from "../../../redux/connector";
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
import {
DEFAULT_FONT,
DEFAULT_MONO_FONT,
Fonts,
FONTS,
FONT_KEYS,
MonospaceFonts,
MONOSPACE_FONTS,
MONOSPACE_FONT_KEYS,
Theme,
ThemeContext,
ThemeOptions,
} from "../../../context/Theme";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import CollapsibleSection from "../../../components/common/CollapsibleSection";
import Tooltip from "../../../components/common/Tooltip";
import Button from "../../../components/ui/Button";
import Checkbox from "../../../components/ui/Checkbox";
import ColourSwatches from "../../../components/ui/ColourSwatches";
import ComboBox from "../../../components/ui/ComboBox";
import InputBox from "../../../components/ui/InputBox";
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
import darkSVG from "../assets/dark.svg";
import lightSVG from "../assets/light.svg";
import mutantSVG from "../assets/mutant_emoji.svg";
import notoSVG from "../assets/noto_emoji.svg";
import openmojiSVG from "../assets/openmoji_emoji.svg";
import twemojiSVG from "../assets/twemoji_emoji.svg";
interface Props {
settings: Settings;
}
import {
ThemeBaseSelectorShim,
ThemeShopShim,
ThemeAccentShim,
DisplayFontShim,
DisplayMonospaceFontShim,
DisplayLigaturesShim,
DisplayEmojiShim,
ThemeCustomCSSShim,
} from "../../../components/settings/AppearanceShims";
import ThemeOverrides from "../../../components/settings/appearance/ThemeOverrides";
import ThemeTools from "../../../components/settings/appearance/ThemeTools";
// ! FIXME: code needs to be rewritten to fix jittering
export function Component(props: Props) {
const theme = useContext(ThemeContext);
const { writeClipboard, openScreen } = useIntermediate();
function setTheme(theme: ThemeOptions) {
dispatch({
type: "SETTINGS_SET_THEME",
theme,
});
}
const pushOverride = useCallback((custom: Partial<Theme>) => {
dispatch({
type: "SETTINGS_SET_THEME_OVERRIDE",
custom,
});
}, []);
function setAccent(accent: string) {
setOverride({
accent,
"scrollbar-thumb": pSBC(-0.2, accent),
});
}
const emojiPack = props.settings.appearance?.emojiPack ?? "mutant";
function setEmojiPack(emojiPack: EmojiPacks) {
dispatch({
type: "SETTINGS_SET_APPEARANCE",
options: {
emojiPack,
},
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const setOverride = useCallback(
debounce(pushOverride as (...args: unknown[]) => void, 200),
[pushOverride],
) as (custom: Partial<Theme>) => void;
const [css, setCSS] = useState(props.settings.theme?.custom?.css ?? "");
useEffect(() => setOverride({ css }), [setOverride, css]);
const selected = props.settings.theme?.base ?? "dark";
export const Appearance = observer(() => {
return (
<div className={styles.appearance}>
<h3>
<Text id="app.settings.pages.appearance.theme" />
</h3>
<div className={styles.themes}>
<div className={styles.theme}>
<img
loading="eager"
src={lightSVG}
draggable={false}
data-active={selected === "light"}
onClick={() =>
selected !== "light" && setTheme({ base: "light" })
}
onContextMenu={(e) => e.preventDefault()}
/>
<h4>
<Text id="app.settings.pages.appearance.color.light" />
</h4>
</div>
<div className={styles.theme}>
<img
loading="eager"
src={darkSVG}
draggable={false}
data-active={selected === "dark"}
onClick={() =>
selected !== "dark" && setTheme({ base: "dark" })
}
onContextMenu={(e) => e.preventDefault()}
/>
<h4>
<Text id="app.settings.pages.appearance.color.dark" />
</h4>
</div>
</div>
<ThemeBaseSelectorShim />
<ThemeShopShim />
<ThemeAccentShim />
{isExperimentEnabled("theme_shop") && (
<Link
to="/settings/theme_shop"
replace
className={styles.focus}>
<CategoryButton
icon={<Store size={24} />}
action="chevron"
description={"Browse themes made by the community"}
hover>
<Text id="app.settings.pages.theme_shop.title" />
</CategoryButton>
</Link>
)}
<DisplayFontShim />
<DisplayLigaturesShim />
<DisplayEmojiShim />
<h3>
<Text id="app.settings.pages.appearance.accent_selector" />
</h3>
<ColourSwatches value={theme.accent} onChange={setAccent} />
{/* TOFIX: Chane this checkbox to turn off the seasonal home page animations*/}
<Checkbox
checked={props.settings.theme?.ligatures === true}
onChange={() =>
setTheme({
ligatures: !props.settings.theme?.ligatures,
})
}
description={
"Displays effects in the home tab during holiday seasons."
}>
Seasonal theme
</Checkbox>
{/*<h3>
<Text id="app.settings.pages.appearance.message_display" />
</h3>
<div className={styles.display}>
<Radio
description={
<Text id="app.settings.pages.appearance.display.default_description" />
}
checked
>
<Text id="app.settings.pages.appearance.display.default" />
</Radio>
<Radio
description={
<Text id="app.settings.pages.appearance.display.compact_description" />
}
disabled
>
<Text id="app.settings.pages.appearance.display.compact" />
</Radio>
</div>*/}
<hr />
{/*<CategoryButton
icon={<Palette size={24} />}
description={"Customize the look of your app using themes."}
action="chevron">
Themes
</CategoryButton>
<CategoryButton
icon={<FontFamily size={24} />}
description={"Change the font and size used in the app."}
action="chevron">
{`Font & text size`}
</CategoryButton>
<CategoryButton
icon={<QuoteLeft size={24} />}
description={"Change the look of your messages."}
action="chevron">
Message Display
</CategoryButton>
<CategoryButton
icon={<HappyBeaming size={24} />}
description={"Personalize your client with an emoji pack."}
action="chevron">
Emoji Packs
</CategoryButton>
<h3>Advanced</h3>
<CategoryButton
icon={<CodeAlt size={24} />}
description={"Customize the client CSS to your heart's content"}
action="chevron">
Custom CSS
</CategoryButton>*/}
<h3>
<Text id="app.settings.pages.appearance.font" />
</h3>
<ComboBox
value={theme.font ?? DEFAULT_FONT}
onChange={(e) =>
pushOverride({ font: e.currentTarget.value as Fonts })
}>
{FONT_KEYS.map((key) => (
<option value={key} key={key}>
{FONTS[key as keyof typeof FONTS].name}
</option>
))}
</ComboBox>
{/* TOFIX: Only show when a font with ligature support is selected, i.e.: Inter.*/}
<Checkbox
checked={props.settings.theme?.ligatures === true}
onChange={() =>
setTheme({
ligatures: !props.settings.theme?.ligatures,
})
}
description={
<Text id="app.settings.pages.appearance.ligatures_desc" />
}>
<Text id="app.settings.pages.appearance.ligatures" />
</Checkbox>
<hr />
<h3>
<Text id="app.settings.pages.appearance.emoji_pack" />
</h3>
<div className={styles.emojiPack}>
<div className={styles.row}>
<div>
<div
className={styles.button}
onClick={() => setEmojiPack("mutant")}
data-active={emojiPack === "mutant"}>
<img
loading="eager"
src={mutantSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>
Mutant Remix{" "}
<a
href="https://mutant.revolt.chat"
target="_blank"
rel="noreferrer">
(by Revolt)
</a>
</h4>
</div>
<div>
<div
className={styles.button}
onClick={() => setEmojiPack("twemoji")}
data-active={emojiPack === "twemoji"}>
<img
loading="eager"
src={twemojiSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>Twemoji</h4>
</div>
</div>
<div className={styles.row}>
<div>
<div
className={styles.button}
onClick={() => setEmojiPack("openmoji")}
data-active={emojiPack === "openmoji"}>
<img
loading="eager"
src={openmojiSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>Openmoji</h4>
</div>
<div>
<div
className={styles.button}
onClick={() => setEmojiPack("noto")}
data-active={emojiPack === "noto"}>
<img
loading="eager"
src={notoSVG}
draggable={false}
onContextMenu={(e) => e.preventDefault()}
/>
</div>
<h4>Noto Emoji</h4>
</div>
</div>
</div>
<hr />
<CollapsibleSection
defaultValue={false}
id="settings_overrides"
summary={<Text id="app.settings.pages.appearance.overrides" />}>
<div className={styles.actions}>
<Tooltip
content={
<Text id="app.settings.pages.appearance.reset_overrides" />
}>
<Button
contrast
iconbutton
onClick={() => setTheme({ custom: {} })}>
<Reset size={22} />
</Button>
</Tooltip>
<div
className={styles.code}
onClick={() => writeClipboard(JSON.stringify(theme))}>
<Tooltip content={<Text id="app.special.copy" />}>
{" "}
{/*TOFIX: Try to put the tooltip above the .code div without messing up the css challenge */}
{JSON.stringify(theme)}
</Tooltip>
</div>
<Tooltip
content={
<Text id="app.settings.pages.appearance.import" />
}>
<Button
contrast
iconbutton
onClick={async () => {
try {
const text =
await navigator.clipboard.readText();
setOverride(JSON.parse(text));
} catch (err) {
openScreen({
id: "_input",
question: (
<Text id="app.settings.pages.appearance.import_theme" />
),
field: (
<Text id="app.settings.pages.appearance.theme_data" />
),
callback: async (string) =>
setOverride(JSON.parse(string)),
});
}
}}>
<Import size={22} />
</Button>
</Tooltip>
</div>
<ThemeTools />
<h3>App</h3>
<div className={styles.overrides}>
{(
[
"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) => (
<div
className={styles.entry}
key={x}
style={{ backgroundColor: theme[x] }}>
<div className={styles.input}>
<input
type="color"
value={theme[x]}
onChange={(v) =>
setOverride({
[x]: v.currentTarget.value,
})
}
/>
</div>
<span
style={`color: ${getContrastingColour(
theme[x],
theme["primary-background"],
)}`}>
{x}
</span>
<div className={styles.override}>
<div
className={styles.picker}
onClick={(e) =>
e.currentTarget.parentElement?.parentElement
?.querySelector("input")
?.click()
}>
<Pencil size={24} />
</div>
<InputBox
type="text"
className={styles.text}
value={theme[x]}
onChange={(y) =>
setOverride({
[x]: y.currentTarget.value,
})
}
/>
</div>
</div>
))}
</div>
<ThemeOverrides />
</CollapsibleSection>
<CollapsibleSection
id="settings_advanced_appearance"
defaultValue={false}
summary={<Text id="app.settings.pages.appearance.advanced" />}>
<h3>
<Text id="app.settings.pages.appearance.mono_font" />
</h3>
<ComboBox
value={theme.monospaceFont ?? DEFAULT_MONO_FONT}
onChange={(e) =>
pushOverride({
monospaceFont: e.currentTarget
.value as MonospaceFonts,
})
}>
{MONOSPACE_FONT_KEYS.map((key) => (
<option value={key} key={key}>
{
MONOSPACE_FONTS[
key as keyof typeof MONOSPACE_FONTS
].name
}
</option>
))}
</ComboBox>
<h3>
<Text id="app.settings.pages.appearance.custom_css" />
</h3>
<TextAreaAutoSize
maxRows={20}
minHeight={480}
code
value={css}
onChange={(ev) => setCSS(ev.currentTarget.value)}
/>
<DisplayMonospaceFontShim />
<ThemeCustomCSSShim />
</CollapsibleSection>
</div>
);
}
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";
}
// <DisplayCompactShim />

View File

@@ -6,8 +6,6 @@ import { TextReact } from "../../../lib/i18n";
import { stopPropagation } from "../../../lib/stopPropagation";
import { voiceState } from "../../../lib/vortex/VoiceState";
import { connectState } from "../../../redux/connector";
import Button from "../../../components/ui/Button";
import ComboBox from "../../../components/ui/ComboBox";
import Overline from "../../../components/ui/Overline";
@@ -20,7 +18,9 @@ import opusSVG from "../assets/opus_logo.svg";
const constraints = { audio: true };
export function Component() {
// TODO: do not rewrite this code until voice is rewritten!
export function Audio() {
const [mediaStream, setMediaStream] = useState<MediaStream | undefined>(
undefined,
);
@@ -244,7 +244,3 @@ function changeAudioDevice(deviceId: string, deviceType: string) {
window.localStorage.setItem("audioOutputDevice", deviceId);
}
}
export const Audio = connectState(Component, () => {
return;
});

View File

@@ -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 (
<div className={styles.experiments}>
<h3>
@@ -25,15 +22,8 @@ export function Component(props: Props) {
{AVAILABLE_EXPERIMENTS.map((key) => (
<Checkbox
key={key}
checked={isExperimentEnabled(key, props.options)}
onChange={(enabled) =>
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}
</Checkbox>
@@ -45,10 +35,4 @@ export function Component(props: Props) {
)}
</div>
);
}
export const ExperimentsPage = connectState(Component, (state) => {
return {
options: state.experiments,
};
});

View File

@@ -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 (
<Checkbox
key={x}
className={styles.entry}
checked={locale === x}
onChange={(v) => {
if (v) {
dispatch({
type: "SET_LOCALE",
locale: x as Language,
});
}
}}>
checked={selected}
onChange={onSelect}>
<div className={styles.flag}>
{lang.i18n === "ta" ? (
<img
@@ -61,36 +62,58 @@ function Entry({ entry: [x, lang], locale }: { entry: Key } & Props) {
);
}
export function Component(props: Props) {
const languages = Object.keys(Langs).map((x) => [
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) => (
<Entry
key={x}
entry={[x, lang]}
selected={language === x}
onSelect={() => locale.setLanguage(x)}
/>
);
return (
<div className={styles.languages}>
@@ -98,11 +121,7 @@ export function Component(props: Props) {
<Text id="app.settings.pages.language.select" />
</h3>
<div className={styles.list}>
{languages
.filter(([, lang]) => !lang.cat)
.map(([x, lang]) => (
<Entry key={x} entry={[x, lang]} {...props} />
))}
{languages.filter(([, lang]) => !lang.cat).map(EntryFactory)}
</div>
<h3>
<Text id="app.settings.pages.language.const" />
@@ -110,9 +129,7 @@ export function Component(props: Props) {
<div className={styles.list}>
{languages
.filter(([, lang]) => lang.cat === "const")
.map(([x, lang]) => (
<Entry key={x} entry={[x, lang]} {...props} />
))}
.map(EntryFactory)}
</div>
<h3>
<Text id="app.settings.pages.language.other" />
@@ -120,9 +137,7 @@ export function Component(props: Props) {
<div className={styles.list}>
{languages
.filter(([, lang]) => lang.cat === "alt")
.map(([x, lang]) => (
<Entry key={x} entry={[x, lang]} {...props} />
))}
.map(EntryFactory)}
</div>
<Tip>
<span>
@@ -137,10 +152,4 @@ export function Component(props: Props) {
</Tip>
</div>
);
}
export const Languages = connectState(Component, (state) => {
return {
locale: state.locale,
};
});

View File

@@ -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 | boolean>(
undefined,
);
@@ -42,10 +31,6 @@ export function Component({ options }: Props) {
});
}, []);
const enabledSounds: SoundOptions = defaultsDeep(
options?.sounds ?? {},
DEFAULT_SOUNDS,
);
return (
<div className={styles.notifications}>
<h3>
@@ -53,7 +38,7 @@ export function Component({ options }: Props) {
</h3>
<Checkbox
disabled={!("Notification" in window)}
checked={options?.desktopEnabled ?? false}
checked={settings.get("notifications:desktop", false)!}
description={
<Text id="app.settings.pages.notifications.descriptions.enable_desktop" />
}
@@ -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);
}}>
<Text id="app.settings.pages.notifications.enable_desktop" />
</Checkbox>
@@ -125,32 +108,16 @@ export function Component({ options }: Props) {
<h3>
<Text id="app.settings.pages.notifications.sounds" />
</h3>
{SOUNDS_ARRAY.map((key) => (
{settings.sounds.getState().map(({ id, enabled }) => (
<Checkbox
key={key}
checked={!!enabledSounds[key]}
key={id}
checked={enabled}
onChange={(enabled) =>
dispatch({
type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
options: {
sounds: {
...options?.sounds,
[key]: enabled,
},
},
})
settings.sounds.setEnabled(id, enabled)
}>
<Text
id={`app.settings.pages.notifications.sound.${key}`}
/>
<Text id={`app.settings.pages.notifications.sound.${id}`} />
</Checkbox>
))}
</div>
);
}
export const Notifications = connectState(Component, (state) => {
return {
options: state.settings.notification,
};
});

View File

@@ -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 {

View File

@@ -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 (
<div className={styles.notifications}>
{/*<h3>
@@ -31,22 +30,13 @@ export function Component(props: Props) {
).map(([key, title]) => (
<Checkbox
key={key}
checked={
(props.options?.disabled ?? []).indexOf(key) === -1
}
checked={sync.isEnabled(key)}
description={
<Text
id={`app.settings.pages.sync.descriptions.${key}`}
/>
}
onChange={(enabled) =>
dispatch({
type: enabled
? "SYNC_ENABLE_KEY"
: "SYNC_DISABLE_KEY",
key,
})
}>
onChange={() => sync.toggle(key)}>
<Text id={`app.settings.pages.${title}`} />
</Checkbox>
))}
@@ -55,10 +45,4 @@ export function Component(props: Props) {
</h5>*/}
</div>
);
}
export const Sync = connectState(Component, (state) => {
return {
options: state.sync,
};
});

View File

@@ -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<Record<string, Theme>>({});
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)}>
<button
class="preview"
onClick={() => {
dispatch({
type: "THEMES_SET_THEME",
theme: {
slug,
meta: theme,
theme: themeData[slug],
},
});
dispatch({
type: "SETTINGS_SET_THEME",
theme: { base: slug },
});
}}>
onClick={() =>
themes.hydrate(themeData[slug], true)
}>
<div class="previewBox">
<div class="hover">Use theme</div>
<ThemePreview