Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions desktop/frontend/src/components/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type Theme,
type ThemeStyle,
} from "../lib/theme";
import { TEXT_SIZES, applyTextSize, getTextSize, type TextSize } from "../lib/textSize";
import type { NetworkView, ProviderView, SettingsView } from "../lib/types";
import { InlineConfirmButton } from "./InlineConfirmButton";
import { ResizableDrawer } from "./ResizableDrawer";
Expand All @@ -36,6 +37,7 @@ export function SettingsPanel({ onClose, onChanged }: { onClose: () => void; onC
const [err, setErr] = useState<string | null>(null);
const [theme, setThemeState] = useState<Theme>(getTheme());
const [themeStyle, setThemeStyleState] = useState<ThemeStyle>(() => getThemeStyle(getTheme()));
const [textSize, setTextSizeState] = useState<TextSize>(getTextSize());
const [tab, setTab] = useState<SettingsTab>("general");

const reload = async () => setS(normalizeSettingsView(await app.Settings().catch(() => null)));
Expand Down Expand Up @@ -106,6 +108,7 @@ export function SettingsPanel({ onClose, onChanged }: { onClose: () => void; onC
<AppearanceSection
theme={theme}
themeStyle={themeStyle}
textSize={textSize}
onTheme={(t) => {
const nextStyle = themeForStyle(themeStyle) === getResolvedTheme(t) ? themeStyle : defaultStyleForTheme(t);
applyTheme(t, nextStyle, { persist: false });
Expand All @@ -120,6 +123,10 @@ export function SettingsPanel({ onClose, onChanged }: { onClose: () => void; onC
setThemeStyleState(style);
void apply(() => app.SetDesktopAppearance(nextTheme, style));
}}
onTextSize={(size) => {
applyTextSize(size);
setTextSizeState(size);
}}
/>
)}
{tab === "updates" && <UpdatesSection configPath={s.configPath} />}
Expand Down Expand Up @@ -984,13 +991,17 @@ function SandboxSection({ s, busy, apply }: SectionProps) {
function AppearanceSection({
theme,
themeStyle,
textSize,
onTheme,
onThemeStyle,
onTextSize,
}: {
theme: Theme;
themeStyle: ThemeStyle;
textSize: TextSize;
onTheme: (t: Theme) => void;
onThemeStyle: (style: ThemeStyle) => void;
onTextSize: (size: TextSize) => void;
}) {
const t = useT();
const themeOptions: Theme[] = ["auto", "light", "dark"];
Expand Down Expand Up @@ -1026,6 +1037,20 @@ function AppearanceSection({
))}
</div>
</div>
<div className="set-row">
<label className="set-label">{t("settings.textSize")}</label>
<div className="set-seg">
{TEXT_SIZES.map((size) => (
<button
key={size}
className={`set-seg__btn${textSize === size ? " set-seg__btn--on" : ""}`}
onClick={() => onTextSize(size)}
>
{textSizeName(size, t)}
</button>
))}
</div>
</div>
</section>
);
}
Expand All @@ -1041,6 +1066,19 @@ function themeName(theme: Theme, t: ReturnType<typeof useT>): string {
}
}

function textSizeName(size: TextSize, t: ReturnType<typeof useT>): string {
switch (size) {
case "small":
return t("settings.textSizeSmall");
case "default":
return t("settings.textSizeDefault");
case "large":
return t("settings.textSizeLarge");
case "xlarge":
return t("settings.textSizeXLarge");
}
}

const MB = 1024 * 1024;
const mb = (n: number) => (n / MB).toFixed(1);

Expand Down
32 changes: 32 additions & 0 deletions desktop/frontend/src/lib/textSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const TEXT_SIZES = ["small", "default", "large", "xlarge"] as const;

export type TextSize = (typeof TEXT_SIZES)[number];

export const DEFAULT_TEXT_SIZE: TextSize = "default";

const TEXT_SIZE_KEY = "reasonix-text-size";

export function isTextSize(value: unknown): value is TextSize {
return typeof value === "string" && (TEXT_SIZES as readonly string[]).includes(value);
}

export function getTextSize(): TextSize {
const stored = typeof localStorage !== "undefined" ? localStorage.getItem(TEXT_SIZE_KEY) : null;
return isTextSize(stored) ? stored : DEFAULT_TEXT_SIZE;
}

export function applyTextSize(size: TextSize): void {
if (typeof document === "undefined") return;
const root = document.documentElement;
if (size === DEFAULT_TEXT_SIZE) root.removeAttribute("data-text-size");
else root.setAttribute("data-text-size", size);
try {
localStorage.setItem(TEXT_SIZE_KEY, size);
} catch {
/* private mode / no storage - the in-DOM attribute still applies */
}
}

export function initTextSize(): void {
applyTextSize(getTextSize());
}
7 changes: 6 additions & 1 deletion desktop/frontend/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ export const en = {
"settings.tab.appearance": "Appearance",
"settings.tab.updates": "Updates",
"settings.providerCount": "{n} providers",
"settings.appearanceMeta": "theme · accent",
"settings.appearanceMeta": "theme · accent · text",
"settings.updatesMeta": "version · config",
"settings.closeBehavior": "When closing window",
"settings.closeBehavior.background": "Keep running",
Expand Down Expand Up @@ -503,6 +503,11 @@ export const en = {
"settings.themeCurrent": "Theme: {theme} / {style}",
"settings.themeChanged": "Theme changed to {theme} / {style}",
"settings.themeUnknown": "Unknown theme: {name}",
"settings.textSize": "Text size",
"settings.textSizeSmall": "Small",
"settings.textSizeDefault": "Default",
"settings.textSizeLarge": "Large",
"settings.textSizeXLarge": "Extra large",
"settings.language": "Language",
"settings.langAuto": "Auto (system)",
"settings.config": "config: {path}",
Expand Down
7 changes: 6 additions & 1 deletion desktop/frontend/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ export const zh: Record<DictKey, string> = {
"settings.tab.appearance": "外观",
"settings.tab.updates": "更新",
"settings.providerCount": "{n} 个服务",
"settings.appearanceMeta": "主题 · 强调色",
"settings.appearanceMeta": "主题 · 强调色 · 字号",
"settings.updatesMeta": "版本 · 配置",
"settings.closeBehavior": "关闭窗口时",
"settings.closeBehavior.background": "保持后台运行",
Expand Down Expand Up @@ -504,6 +504,11 @@ export const zh: Record<DictKey, string> = {
"settings.themeCurrent": "当前主题:{theme} / {style}",
"settings.themeChanged": "已切换主题为 {theme} / {style}",
"settings.themeUnknown": "未知主题:{name}",
"settings.textSize": "字号",
"settings.textSizeSmall": "小",
"settings.textSizeDefault": "默认",
"settings.textSizeLarge": "大",
"settings.textSizeXLarge": "特大",
"settings.language": "语言",
"settings.langAuto": "自动(跟随系统)",
"settings.config": "配置文件:{path}",
Expand Down
2 changes: 2 additions & 0 deletions desktop/frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import App from "./App";
import { ErrorBoundary } from "./components/ErrorBoundary";
import { installGlobalCrashHandlers } from "./lib/crash";
import { LocaleProvider } from "./lib/i18n";
import { initTextSize } from "./lib/textSize";
import { initTheme } from "./lib/theme";
import "./styles.css";

// Apply the saved appearance (auto/light/dark) before the first paint.
initTheme();
initTextSize();

// Pre-warm font fallback stacks so the first frame doesn't flicker between the
// browser default font and the app's configured typeface. Inserting a hidden span
Expand Down
Loading
Loading