chore(react/settings): port font family settings

This commit is contained in:
Elian Doran 2025-08-14 19:20:25 +03:00
parent 83b22b4861
commit d2bc72d54f
No known key found for this signature in database
4 changed files with 153 additions and 98 deletions

View File

@ -1,27 +1,74 @@
interface FormSelectProps<T> {
currentValue?: string;
onChange(newValue: string): void;
values: T[];
keyProperty: keyof T;
titleProperty: keyof T;
import type { ComponentChildren } from "preact";
type OnChangeListener = (newValue: string) => void;
interface FormSelectGroup<T> {
title: string;
items: T[];
}
export default function FormSelect<T>({ currentValue, values, onChange, keyProperty, titleProperty }: FormSelectProps<T>) {
interface ValueConfig<T, Q> {
values: Q[];
/** The property of an item of {@link values} to be used as the key, uniquely identifying it. The key will be passed to the change listener. */
keyProperty: keyof T;
/** The property of an item of {@link values} to be used as the label, representing a human-readable version of the key. If missing, {@link keyProperty} will be used instead. */
titleProperty?: keyof T;
/** The current value of the combobox. The value will be looked up by going through {@link values} and looking an item whose {@link #keyProperty} value matches this one */
currentValue?: string;
}
interface FormSelectProps<T, Q> extends ValueConfig<T, Q> {
onChange: OnChangeListener;
}
/**
* Combobox component that takes in any object array as data. Each item of the array is rendered as an item, and the key and values are obtained by looking into the object by a specified key.
*/
export default function FormSelect<T>(props: FormSelectProps<T, T>) {
return (
<FormSelectBody onChange={props.onChange}>
<FormSelectGroup {...props} />
</FormSelectBody>
);
}
/**
* Similar to {@link FormSelect}, but the top-level elements are actually groups.
*/
export function FormSelectWithGroups<T>({ values, keyProperty, titleProperty, currentValue, onChange }: FormSelectProps<T, FormSelectGroup<T>>) {
return (
<FormSelectBody onChange={onChange}>
{values.map(({ title, items }) => {
return (
<optgroup label={title}>
<FormSelectGroup values={items} keyProperty={keyProperty} titleProperty={titleProperty} currentValue={currentValue} />
</optgroup>
);
})}
</FormSelectBody>
)
}
function FormSelectBody({ children, onChange }: { children: ComponentChildren, onChange: OnChangeListener }) {
return (
<select
class="form-select"
onChange={e => onChange((e.target as HTMLInputElement).value)}
>
{values.map(item => {
return (
<option
value={item[keyProperty] as any}
selected={item[keyProperty] === currentValue}
>
{item[titleProperty] as any}
</option>
);
})}
{children}
</select>
)
}
function FormSelectGroup<T>({ values, keyProperty, titleProperty, currentValue }: ValueConfig<T, T>) {
return values.map(item => {
return (
<option
value={item[keyProperty] as any}
selected={item[keyProperty] === currentValue}
>
{item[titleProperty ?? keyProperty] ?? item[keyProperty] as any}
</option>
);
});
}

View File

@ -3,11 +3,13 @@ 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 FormSelect, { FormSelectWithGroups } from "../../react/FormSelect";
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import OptionsSection from "./components/OptionsSection";
import server from "../../../services/server";
import FormCheckbox from "../../react/FormCheckbox";
import FormGroup from "../../react/FormGroup";
import { FontFamily, OptionNames } from "@triliumnext/commons";
interface Theme {
val: string;
@ -24,11 +26,59 @@ const BUILTIN_THEMES: Theme[] = [
{ val: "dark", title: t("theme.dark_theme") }
]
interface FontFamilyEntry {
value: FontFamily;
label?: string;
}
interface FontGroup {
title: string;
items: FontFamilyEntry[];
}
const FONT_FAMILIES: FontGroup[] = [
{
title: t("fonts.generic-fonts"),
items: [
{ value: "theme", label: t("fonts.theme_defined") },
{ value: "system", label: t("fonts.system-default") },
{ value: "serif", label: t("fonts.serif") },
{ value: "sans-serif", label: t("fonts.sans-serif") },
{ value: "monospace", label: t("fonts.monospace") }
]
},
{
title: t("fonts.sans-serif-system-fonts"),
items: [{ value: "Arial" }, { value: "Verdana" }, { value: "Helvetica" }, { value: "Tahoma" }, { value: "Trebuchet MS" }, { value: "Microsoft YaHei" }]
},
{
title: t("fonts.serif-system-fonts"),
items: [{ value: "Times New Roman" }, { value: "Georgia" }, { value: "Garamond" }]
},
{
title: t("fonts.monospace-system-fonts"),
items: [
{ value: "Courier New" },
{ value: "Brush Script MT" },
{ value: "Impact" },
{ value: "American Typewriter" },
{ value: "Andalé Mono" },
{ value: "Lucida Console" },
{ value: "Monaco" }
]
},
{
title: t("fonts.handwriting-system-fonts"),
items: [{ value: "Bradley Hand" }, { value: "Luminari" }, { value: "Comic Sans MS" }]
}
];
export default function AppearanceSettings() {
return (
<>
<LayoutOrientation />
<ApplicationTheme />
<Fonts />
</>
)
}
@ -75,7 +125,10 @@ function ApplicationTheme() {
<OptionsSection title={t("theme.title")}>
<Column>
<label>{t("theme.theme_label")}</label>
<FormSelect values={themes} currentValue={theme} onChange={setTheme} />
<FormSelect
values={themes} currentValue={theme} onChange={setTheme}
keyProperty="val" titleProperty="title"
/>
</Column>
<Column className="side-checkbox">
@ -86,4 +139,35 @@ function ApplicationTheme() {
</Column>
</OptionsSection>
)
}
function Fonts() {
return (
<OptionsSection title={t("fonts.fonts")}>
<Font title={t("fonts.main_font")} fontFamilyOption="mainFontFamily" />
<Font title={t("fonts.note_tree_font")} fontFamilyOption="treeFontFamily" />
<Font title={t("fonts.note_detail_font")} fontFamilyOption="detailFontFamily" />
<Font title={t("fonts.monospace_font")} fontFamilyOption="monospaceFontFamily" />
</OptionsSection>
);
}
function Font({ title, fontFamilyOption }: { title: string, fontFamilyOption: OptionNames }) {
const [ fontFamily, setFontFamily ] = useTriliumOption(fontFamilyOption);
return (
<>
<h5>{title}</h5>
<FormGroup>
<Column>
<label>{t("fonts.font_family")}</label>
<FormSelectWithGroups
values={FONT_FAMILIES}
currentValue={fontFamily} onChange={setFontFamily}
keyProperty="value" titleProperty="label"
/>
</Column>
</FormGroup>
</>
);
}

View File

@ -1,64 +1,14 @@
import OptionsWidget from "../options_widget.js";
import utils from "../../../../services/utils.js";
import { t } from "../../../../services/i18n.js";
import type { FontFamily, OptionMap, OptionNames } from "@triliumnext/commons";
import type { OptionMap, OptionNames } from "@triliumnext/commons";
interface FontFamilyEntry {
value: FontFamily;
label?: string;
}
interface FontGroup {
title: string;
items: FontFamilyEntry[];
}
const FONT_FAMILIES: FontGroup[] = [
{
title: t("fonts.generic-fonts"),
items: [
{ value: "theme", label: t("fonts.theme_defined") },
{ value: "system", label: t("fonts.system-default") },
{ value: "serif", label: t("fonts.serif") },
{ value: "sans-serif", label: t("fonts.sans-serif") },
{ value: "monospace", label: t("fonts.monospace") }
]
},
{
title: t("fonts.sans-serif-system-fonts"),
items: [{ value: "Arial" }, { value: "Verdana" }, { value: "Helvetica" }, { value: "Tahoma" }, { value: "Trebuchet MS" }, { value: "Microsoft YaHei" }]
},
{
title: t("fonts.serif-system-fonts"),
items: [{ value: "Times New Roman" }, { value: "Georgia" }, { value: "Garamond" }]
},
{
title: t("fonts.monospace-system-fonts"),
items: [
{ value: "Courier New" },
{ value: "Brush Script MT" },
{ value: "Impact" },
{ value: "American Typewriter" },
{ value: "Andalé Mono" },
{ value: "Lucida Console" },
{ value: "Monaco" }
]
},
{
title: t("fonts.handwriting-system-fonts"),
items: [{ value: "Bradley Hand" }, { value: "Luminari" }, { value: "Comic Sans MS" }]
}
];
const TPL = /*html*/`
<div class="options-section">
<h4>${t("fonts.fonts")}</h4>
<h5>${t("fonts.main_font")}</h5>
<div class="form-group row">
<div class="col-4">
<label for="main-font-family">${t("fonts.font_family")}</label>
<select id="main-font-family" class="main-font-family form-select"></select>
</div>
@ -72,8 +22,6 @@ const TPL = /*html*/`
</div>
</div>
<h5>${t("fonts.note_tree_font")}</h5>
<div class="form-group row">
<div class="col-4">
<label for="tree-font-family">${t("fonts.font_family")}</label>
@ -90,8 +38,6 @@ const TPL = /*html*/`
</div>
</div>
<h5>${t("fonts.note_detail_font")}</h5>
<div class="form-group row">
<div class="col-4">
<label for="detail-font-family">${t("fonts.font_family")}</label>
@ -108,8 +54,6 @@ const TPL = /*html*/`
</div>
</div>
<h5>${t("fonts.monospace_font")}</h5>
<div class="form-group row">
<div class="col-4">
<label for="monospace-font-family">${t("fonts.font_family")}</label>
@ -189,7 +133,7 @@ export default class FontsOptions extends OptionsWidget {
this.$monospaceFontSize.val(options.monospaceFontSize);
this.fillFontFamilyOptions(this.$monospaceFontFamily, options.monospaceFontFamily);
const optionsToSave: OptionNames[] = ["mainFontFamily", "mainFontSize", "treeFontFamily", "treeFontSize", "detailFontFamily", "detailFontSize", "monospaceFontFamily", "monospaceFontSize"];
const optionsToSave: OptionNames[] = ["mainFontSize", "treeFontSize", "monospaceFontSize"];
for (const optionName of optionsToSave) {
const $el = (this as any)[`$${optionName}`];
@ -197,22 +141,4 @@ export default class FontsOptions extends OptionsWidget {
}
}
fillFontFamilyOptions($select: JQuery<HTMLElement>, currentValue: string) {
$select.empty();
for (const { title, items } of Object.values(FONT_FAMILIES)) {
const $group = $("<optgroup>").attr("label", title);
for (const { value, label } of items) {
$group.append(
$("<option>")
.attr("value", value)
.prop("selected", value === currentValue)
.text(label ?? value)
);
}
$select.append($group);
}
}
}

View File

@ -10,9 +10,7 @@ export default function OptionsSection({ title, children }: OptionsSectionProps)
<div className="options-section">
<h4>{title}</h4>
<div className="form-group row">
{children}
</div>
{children}
</div>
);
}