diff --git a/apps/client/src/widgets/react/Column.tsx b/apps/client/src/widgets/react/Column.tsx new file mode 100644 index 000000000..682d750d6 --- /dev/null +++ b/apps/client/src/widgets/react/Column.tsx @@ -0,0 +1,14 @@ +import type { ComponentChildren } from "preact"; + +interface ColumnProps { + md: number; + children: ComponentChildren; +} + +export default function Column({ md, children }: ColumnProps) { + return ( +
+ {children} +
+ ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/react/FormSelect.tsx b/apps/client/src/widgets/react/FormSelect.tsx new file mode 100644 index 000000000..1a13d1571 --- /dev/null +++ b/apps/client/src/widgets/react/FormSelect.tsx @@ -0,0 +1,25 @@ +interface FormSelectProps { + currentValue?: string; + onChange(newValue: string): void; + values: { val: string, title: string }[]; +} + +export default function FormSelect({ currentValue, values, onChange }: FormSelectProps) { + return ( + + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/react/ReactBasicWidget.tsx b/apps/client/src/widgets/react/ReactBasicWidget.tsx index 18e9b87ec..f25a3b71e 100644 --- a/apps/client/src/widgets/react/ReactBasicWidget.tsx +++ b/apps/client/src/widgets/react/ReactBasicWidget.tsx @@ -28,5 +28,5 @@ export function renderReactWidget(parentComponent: Component, el: JSX.Element) { {el} ), renderContainer); - return $(renderContainer.firstChild as HTMLElement); + return $(renderContainer.children) as JQuery; } \ No newline at end of file diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index c275f5a40..a99fa45dc 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -4,7 +4,7 @@ import { ParentComponent } from "./ReactBasicWidget"; import SpacedUpdate from "../../services/spaced_update"; import { OptionNames } from "@triliumnext/commons"; import options from "../../services/options"; -import utils from "../../services/utils"; +import utils, { reloadFrontendApp } from "../../services/utils"; /** * Allows a React component to react to Trilium events (e.g. `entitiesReloaded`). When the desired event is triggered, the handler is invoked with the event parameters. @@ -68,12 +68,16 @@ export function useSpacedUpdate(callback: () => Promise, interval = 1000) return spacedUpdateRef.current; } -export function useTriliumOption(name: OptionNames): [string, (newValue: string) => Promise] { +export function useTriliumOption(name: OptionNames, needsRefresh?: boolean): [string, (newValue: string) => Promise] { const initialValue = options.get(name); const [ value, setValue ] = useState(initialValue); async function wrappedSetValue(newValue: string) { await options.save(name, newValue); + + if (needsRefresh) { + reloadFrontendApp(`option change: ${name}`); + } }; useTriliumEvent("entitiesReloaded", ({ loadResults }) => { diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index 6743c4a81..9d5fa674d 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -1,31 +1,68 @@ +import { useEffect, useState } from "preact/hooks"; import { t } from "../../../services/i18n"; import { isMobile, reloadFrontendApp } from "../../../services/utils"; +import Column from "../../react/Column"; import FormRadioGroup from "../../react/FormRadioGroup"; +import FormSelect from "../../react/FormSelect"; import { useTriliumOption } from "../../react/hooks"; import OptionsSection from "./components/OptionsSection"; +import server from "../../../services/server"; + +interface Theme { + val: string; + title: string; + noteId?: string; +} + +const BUILTIN_THEMES: Theme[] = [ + { val: "next", title: t("theme.triliumnext") }, + { val: "next-light", title: t("theme.triliumnext-light") }, + { val: "next-dark", title: t("theme.triliumnext-dark") }, + { val: "auto", title: t("theme.auto_theme") }, + { val: "light", title: t("theme.light_theme") }, + { val: "dark", title: t("theme.dark_theme") } +] export default function AppearanceSettings() { - const [ layoutOrientation, setLayoutOrientation ] = useTriliumOption("layoutOrientation"); + const [ layoutOrientation, setLayoutOrientation ] = useTriliumOption("layoutOrientation", true); + const [ theme, setTheme ] = useTriliumOption("theme", true); + + const [ themes, setThemes ] = useState([]); + + useEffect(() => { + server.get("options/user-themes").then((userThemes) => { + setThemes([ + ...BUILTIN_THEMES, + ...userThemes + ]) + }); + }, []); return ( - - {!isMobile() && {t("theme.layout-vertical-title")} - {t("theme.layout-vertical-description")}, - value: "vertical" - }, - { - label: <>{t("theme.layout-horizontal-title")} - {t("theme.layout-horizontal-description")}, - value: "horizontal" - } - ]} - currentValue={layoutOrientation} onChange={async (newValue) => { - await setLayoutOrientation(newValue); - reloadFrontendApp("layout orientation change"); - }} - />} - + <> + + {!isMobile() && {t("theme.layout-vertical-title")} - {t("theme.layout-vertical-description")}, + value: "vertical" + }, + { + label: <>{t("theme.layout-horizontal-title")} - {t("theme.layout-horizontal-description")}, + value: "horizontal" + } + ]} + currentValue={layoutOrientation} onChange={setLayoutOrientation} + />} + + + + + + + + + ) } \ No newline at end of file diff --git a/apps/client/src/widgets/type_widgets/options/appearance/theme.ts b/apps/client/src/widgets/type_widgets/options/appearance/theme.ts index 66903babb..a183815ed 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance/theme.ts +++ b/apps/client/src/widgets/type_widgets/options/appearance/theme.ts @@ -6,14 +6,7 @@ import type { OptionMap } from "@triliumnext/commons"; const TPL = /*html*/`
-

${t("theme.title")}

-
-
- - -
-
`; -interface Theme { - val: string; - title: string; - noteId?: string; -} - export default class ThemeOptions extends OptionsWidget { - private $themeSelect!: JQuery; private $overrideThemeFonts!: JQuery; - private $layoutOrientation!: JQuery; doRender() { this.$widget = $(TPL); this.$themeSelect = this.$widget.find(".theme-select"); this.$overrideThemeFonts = this.$widget.find(".override-theme-fonts"); - this.$themeSelect.on("change", async () => { - const newTheme = this.$themeSelect.val(); - - await server.put(`options/theme/${newTheme}`); - - utils.reloadFrontendApp("theme change"); - }); this.$overrideThemeFonts.on("change", () => this.updateCheckboxOption("overrideThemeFonts", this.$overrideThemeFonts)); } async optionsLoaded(options: OptionMap) { - const themes: Theme[] = [ - { val: "next", title: t("theme.triliumnext") }, - { val: "next-light", title: t("theme.triliumnext-light") }, - { val: "next-dark", title: t("theme.triliumnext-dark") }, - { val: "auto", title: t("theme.auto_theme") }, - { val: "light", title: t("theme.light_theme") }, - { val: "dark", title: t("theme.dark_theme") } - ].concat(await server.get("options/user-themes")); - - this.$themeSelect.empty(); - - for (const theme of themes) { - this.$themeSelect.append( - $("