ui: fix theme and locale review regressions
This commit is contained in:
parent
7bdf866403
commit
37a914bb88
@ -2,6 +2,7 @@ import type { TranslationMap } from "../lib/types.ts";
|
||||
|
||||
export const en: TranslationMap = {
|
||||
common: {
|
||||
version: "Version",
|
||||
health: "Health",
|
||||
ok: "OK",
|
||||
offline: "Offline",
|
||||
|
||||
@ -11,6 +11,7 @@ export const pt_BR: TranslationMap = {
|
||||
disabled: "Desativado",
|
||||
na: "n/a",
|
||||
docs: "Docs",
|
||||
theme: "Tema",
|
||||
resources: "Recursos",
|
||||
search: "Pesquisar",
|
||||
},
|
||||
|
||||
@ -11,6 +11,7 @@ export const zh_CN: TranslationMap = {
|
||||
disabled: "已禁用",
|
||||
na: "不适用",
|
||||
docs: "文档",
|
||||
theme: "主题",
|
||||
resources: "资源",
|
||||
search: "搜索",
|
||||
},
|
||||
|
||||
@ -11,6 +11,7 @@ export const zh_TW: TranslationMap = {
|
||||
disabled: "已禁用",
|
||||
na: "不適用",
|
||||
docs: "文檔",
|
||||
theme: "主題",
|
||||
resources: "資源",
|
||||
search: "搜尋",
|
||||
},
|
||||
|
||||
@ -128,11 +128,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
sessionKey: "agent",
|
||||
lastActiveSessionKey: "agent",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(sessionStorage.length).toBe(0);
|
||||
@ -151,11 +153,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
token: "session-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
|
||||
@ -178,11 +182,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
token: "gateway-a-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
|
||||
@ -192,11 +198,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
gatewayUrl: "wss://other-gateway.example:8443/openclaw",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
}),
|
||||
);
|
||||
@ -220,11 +228,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
token: "memory-only-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(loadSettings()).toMatchObject({
|
||||
@ -236,11 +246,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(sessionStorage.length).toBe(1);
|
||||
@ -259,11 +271,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
token: "stale-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
saveSettings({
|
||||
@ -271,15 +285,47 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
token: "",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
|
||||
expect(loadSettings().token).toBe("");
|
||||
expect(sessionStorage.length).toBe(0);
|
||||
});
|
||||
|
||||
it("persists themeMode and navWidth alongside the selected theme", async () => {
|
||||
setTestLocation({
|
||||
protocol: "https:",
|
||||
host: "gateway.example:8443",
|
||||
pathname: "/",
|
||||
});
|
||||
|
||||
const { saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "dash",
|
||||
themeMode: "light",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 320,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
|
||||
expect(JSON.parse(localStorage.getItem("openclaw.control.settings.v1") ?? "{}")).toMatchObject({
|
||||
theme: "dash",
|
||||
themeMode: "light",
|
||||
navWidth: 320,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -19,7 +19,7 @@ export type UiSettings = {
|
||||
chatShowThinking: boolean;
|
||||
splitRatio: number; // Sidebar split ratio (0.4 to 0.7, default 0.6)
|
||||
navCollapsed: boolean; // Collapsible sidebar state
|
||||
navWidth: number; // Sidebar width when expanded (240–400px)
|
||||
navWidth: number; // Sidebar width when expanded (200–400px)
|
||||
navGroupsCollapsed: Record<string, boolean>; // Which nav groups are collapsed
|
||||
locale?: string;
|
||||
};
|
||||
@ -194,10 +194,12 @@ function persistSettings(next: UiSettings) {
|
||||
sessionKey: next.sessionKey,
|
||||
lastActiveSessionKey: next.lastActiveSessionKey,
|
||||
theme: next.theme,
|
||||
themeMode: next.themeMode,
|
||||
chatFocusMode: next.chatFocusMode,
|
||||
chatShowThinking: next.chatShowThinking,
|
||||
splitRatio: next.splitRatio,
|
||||
navCollapsed: next.navCollapsed,
|
||||
navWidth: next.navWidth,
|
||||
navGroupsCollapsed: next.navGroupsCollapsed,
|
||||
...(next.locale ? { locale: next.locale } : {}),
|
||||
};
|
||||
|
||||
@ -9,6 +9,8 @@ export type ThemeTransitionContext = {
|
||||
export type ThemeTransitionOptions = {
|
||||
nextTheme: ResolvedTheme;
|
||||
applyTheme: () => void;
|
||||
// Retained so callers from stacked slices can keep passing pointer metadata
|
||||
// while theme switching remains an immediate, non-animated update here.
|
||||
context?: ThemeTransitionContext;
|
||||
currentTheme?: ResolvedTheme | null;
|
||||
};
|
||||
|
||||
33
ui/src/ui/theme.test.ts
Normal file
33
ui/src/ui/theme.test.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { parseThemeSelection, resolveTheme } from "./theme.ts";
|
||||
|
||||
describe("resolveTheme", () => {
|
||||
it("keeps the legacy mode-only signature working for existing callers", () => {
|
||||
expect(resolveTheme("dark")).toBe("dark");
|
||||
expect(resolveTheme("light")).toBe("light");
|
||||
});
|
||||
|
||||
it("resolves named theme families when mode is provided", () => {
|
||||
expect(resolveTheme("knot", "dark")).toBe("openknot");
|
||||
expect(resolveTheme("dash", "light")).toBe("dash-light");
|
||||
});
|
||||
|
||||
it("uses system preference when a named theme omits mode", () => {
|
||||
vi.stubGlobal("matchMedia", vi.fn().mockReturnValue({ matches: true }));
|
||||
expect(resolveTheme("knot")).toBe("openknot-light");
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseThemeSelection", () => {
|
||||
it("maps legacy stored values onto theme + mode", () => {
|
||||
expect(parseThemeSelection("system", undefined)).toEqual({
|
||||
theme: "claw",
|
||||
mode: "system",
|
||||
});
|
||||
expect(parseThemeSelection("fieldmanual", undefined)).toEqual({
|
||||
theme: "dash",
|
||||
mode: "dark",
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -62,12 +62,31 @@ function resolveMode(mode: ThemeMode): "light" | "dark" {
|
||||
return mode;
|
||||
}
|
||||
|
||||
export function resolveTheme(theme: ThemeName, mode: ThemeMode): ResolvedTheme {
|
||||
const resolvedMode = resolveMode(mode);
|
||||
if (theme === "claw") {
|
||||
function normalizeThemeArgs(
|
||||
themeOrMode: ThemeName | ThemeMode,
|
||||
mode: ThemeMode | undefined,
|
||||
): { theme: ThemeName; mode: ThemeMode } {
|
||||
if (VALID_THEME_NAMES.has(themeOrMode as ThemeName)) {
|
||||
return {
|
||||
theme: themeOrMode as ThemeName,
|
||||
mode: mode ?? "system",
|
||||
};
|
||||
}
|
||||
return {
|
||||
theme: "claw",
|
||||
mode: themeOrMode as ThemeMode,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveTheme(mode: ThemeMode): ResolvedTheme;
|
||||
export function resolveTheme(theme: ThemeName, mode?: ThemeMode): ResolvedTheme;
|
||||
export function resolveTheme(themeOrMode: ThemeName | ThemeMode, mode?: ThemeMode): ResolvedTheme {
|
||||
const normalized = normalizeThemeArgs(themeOrMode, mode);
|
||||
const resolvedMode = resolveMode(normalized.mode);
|
||||
if (normalized.theme === "claw") {
|
||||
return resolvedMode === "light" ? "light" : "dark";
|
||||
}
|
||||
if (theme === "knot") {
|
||||
if (normalized.theme === "knot") {
|
||||
return resolvedMode === "light" ? "openknot-light" : "openknot";
|
||||
}
|
||||
return resolvedMode === "light" ? "dash-light" : "dash";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user