дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
Adds Solarized Light, Solarized Dark, One Light and One Dark as built-in themes across the workbench, editor, terminal and Mermaid, keeping the original palettes (MIT notices in THIRD_PARTY_LICENSES.md, mapping in docs/themes.md). - Native window colours are read from tokens.css at build time (`theme_frame.rs`) instead of a second colour table. - Startup restores the built-in palette before first paint. - Editor schemes, the terminal and Mermaid refresh on switches between themes of the same kind. - The plugin packages of the same themes are removed. - A test requires every built-in palette theme to define each token (only listed ones may inherit from the base theme), so new tokens can't silently miss them. Co-authored-by: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
169 рядки
6.1 KiB
TypeScript
169 рядки
6.1 KiB
TypeScript
/**
|
|
* Appearance & Behavior settings beyond the basics in `schema.ts`: extra
|
|
* Appearance / System Settings fields and the Notifications page. Mirrors the
|
|
* `ide-core::settings` defaults; validation never throws.
|
|
*/
|
|
|
|
import { BUILTIN_THEME_IDS, type BuiltinThemeId } from "@/platform/themes";
|
|
|
|
export type Antialiasing = "subpixel" | "greyscale" | "none";
|
|
export type OpenProjectIn = "ask" | "new" | "same";
|
|
export type NotificationDisplay = "balloon" | "sticky" | "none";
|
|
/** Themes selectable as "preferred" when syncing with the OS. */
|
|
export type ConcreteTheme = BuiltinThemeId;
|
|
|
|
export interface AppearanceExtras {
|
|
syncWithOs: boolean;
|
|
preferredLightTheme: ConcreteTheme;
|
|
preferredDarkTheme: ConcreteTheme;
|
|
zoom: number;
|
|
useCustomFont: boolean;
|
|
uiFontFamily: string;
|
|
contrastScrollbars: boolean;
|
|
redGreenVision: boolean;
|
|
differentToolWindowBackground: boolean;
|
|
compactMode: boolean;
|
|
fullPathInTitle: boolean;
|
|
projectColorsInToolbar: boolean;
|
|
dndWithAltOnly: boolean;
|
|
menuIcons: boolean;
|
|
ideAntialiasing: Antialiasing;
|
|
editorAntialiasing: Antialiasing;
|
|
showToolWindowBars: boolean;
|
|
showToolWindowNames: boolean;
|
|
widescreenToolWindows: boolean;
|
|
}
|
|
|
|
export interface DateFormatValid {
|
|
overrideSystem: boolean;
|
|
pattern: string;
|
|
use24Hour: boolean;
|
|
pretty: boolean;
|
|
}
|
|
|
|
export interface GeneralExtras {
|
|
openProjectIn: OpenProjectIn;
|
|
defaultProjectDirectory: string;
|
|
saveOnIdle: boolean;
|
|
idleSeconds: number;
|
|
dateFormat: DateFormatValid;
|
|
}
|
|
|
|
export interface NotificationGroupValid {
|
|
display: NotificationDisplay;
|
|
log: boolean;
|
|
}
|
|
|
|
export interface NotificationsValid {
|
|
balloons: boolean;
|
|
sound: boolean;
|
|
groups: Record<string, NotificationGroupValid>;
|
|
}
|
|
|
|
export const ZOOM_LIMITS = { min: 50, max: 300 } as const;
|
|
export const IDLE_LIMITS = { min: 1, max: 3600 } as const;
|
|
|
|
export const DEFAULT_APPEARANCE_EXTRAS: AppearanceExtras = {
|
|
syncWithOs: false,
|
|
preferredLightTheme: "light",
|
|
preferredDarkTheme: "dark",
|
|
zoom: 100,
|
|
useCustomFont: false,
|
|
uiFontFamily: "Inter",
|
|
contrastScrollbars: false,
|
|
redGreenVision: false,
|
|
differentToolWindowBackground: false,
|
|
compactMode: false,
|
|
fullPathInTitle: false,
|
|
projectColorsInToolbar: true,
|
|
dndWithAltOnly: false,
|
|
menuIcons: true,
|
|
ideAntialiasing: "greyscale",
|
|
editorAntialiasing: "greyscale",
|
|
showToolWindowBars: true,
|
|
showToolWindowNames: true,
|
|
widescreenToolWindows: false,
|
|
};
|
|
|
|
export const DEFAULT_GENERAL_EXTRAS: GeneralExtras = {
|
|
openProjectIn: "ask",
|
|
defaultProjectDirectory: "",
|
|
saveOnIdle: false,
|
|
idleSeconds: 15,
|
|
dateFormat: { overrideSystem: false, pattern: "dd.MM.yyyy", use24Hour: true, pretty: true },
|
|
};
|
|
|
|
export const DEFAULT_NOTIFICATIONS: NotificationsValid = { balloons: true, sound: false, groups: {} };
|
|
|
|
type Obj = Record<string, unknown>;
|
|
const obj = (v: unknown): Obj => (v && typeof v === "object" && !Array.isArray(v) ? (v as Obj) : {});
|
|
const bool = (v: unknown, d: boolean) => (typeof v === "boolean" ? v : d);
|
|
const oneOf = <T extends string>(v: unknown, allowed: readonly T[], d: T): T =>
|
|
typeof v === "string" && (allowed as readonly string[]).includes(v) ? (v as T) : d;
|
|
const int = (v: unknown, d: number, { min, max }: { min: number; max: number }) =>
|
|
typeof v === "number" && Number.isFinite(v) ? Math.round(Math.min(max, Math.max(min, v))) : d;
|
|
|
|
const AA: readonly Antialiasing[] = ["subpixel", "greyscale", "none"];
|
|
const CONCRETE = BUILTIN_THEME_IDS;
|
|
const DISPLAYS: readonly NotificationDisplay[] = ["balloon", "sticky", "none"];
|
|
|
|
export function validateAppearanceExtras(raw: unknown): AppearanceExtras {
|
|
const a = obj(raw);
|
|
const d = DEFAULT_APPEARANCE_EXTRAS;
|
|
const b = (k: keyof AppearanceExtras) => bool(a[k], d[k] as boolean);
|
|
return {
|
|
// A legacy `theme = "system"` means syncing too.
|
|
syncWithOs: bool(a.syncWithOs, d.syncWithOs) || a.theme === "system",
|
|
preferredLightTheme: oneOf(a.preferredLightTheme, CONCRETE, d.preferredLightTheme),
|
|
preferredDarkTheme: oneOf(a.preferredDarkTheme, CONCRETE, d.preferredDarkTheme),
|
|
zoom: int(a.zoom, d.zoom, ZOOM_LIMITS),
|
|
useCustomFont: b("useCustomFont"),
|
|
uiFontFamily: typeof a.uiFontFamily === "string" && a.uiFontFamily.trim() ? a.uiFontFamily.trim() : d.uiFontFamily,
|
|
contrastScrollbars: b("contrastScrollbars"),
|
|
redGreenVision: b("redGreenVision"),
|
|
differentToolWindowBackground: b("differentToolWindowBackground"),
|
|
compactMode: b("compactMode"),
|
|
fullPathInTitle: b("fullPathInTitle"),
|
|
projectColorsInToolbar: b("projectColorsInToolbar"),
|
|
dndWithAltOnly: b("dndWithAltOnly"),
|
|
menuIcons: b("menuIcons"),
|
|
ideAntialiasing: oneOf(a.ideAntialiasing, AA, d.ideAntialiasing),
|
|
editorAntialiasing: oneOf(a.editorAntialiasing, AA, d.editorAntialiasing),
|
|
showToolWindowBars: b("showToolWindowBars"),
|
|
showToolWindowNames: b("showToolWindowNames"),
|
|
widescreenToolWindows: b("widescreenToolWindows"),
|
|
};
|
|
}
|
|
|
|
export function validateGeneralExtras(raw: unknown): GeneralExtras {
|
|
const g = obj(raw);
|
|
const d = DEFAULT_GENERAL_EXTRAS;
|
|
const df = obj(g.dateFormat);
|
|
return {
|
|
openProjectIn: oneOf(g.openProjectIn, ["ask", "new", "same"] as const, d.openProjectIn),
|
|
defaultProjectDirectory: typeof g.defaultProjectDirectory === "string" ? g.defaultProjectDirectory : "",
|
|
saveOnIdle: bool(g.saveOnIdle, d.saveOnIdle),
|
|
idleSeconds: int(g.idleSeconds, d.idleSeconds, IDLE_LIMITS),
|
|
dateFormat: {
|
|
overrideSystem: bool(df.overrideSystem, d.dateFormat.overrideSystem),
|
|
pattern: typeof df.pattern === "string" && df.pattern.trim() ? df.pattern : d.dateFormat.pattern,
|
|
use24Hour: bool(df.use24Hour, d.dateFormat.use24Hour),
|
|
pretty: bool(df.pretty, d.dateFormat.pretty),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function validateNotifications(raw: unknown): NotificationsValid {
|
|
const n = obj(raw);
|
|
const groups: Record<string, NotificationGroupValid> = {};
|
|
for (const [id, v] of Object.entries(obj(n.groups))) {
|
|
const g = obj(v);
|
|
groups[id] = { display: oneOf(g.display, DISPLAYS, "balloon"), log: bool(g.log, true) };
|
|
}
|
|
return {
|
|
balloons: bool(n.balloons, DEFAULT_NOTIFICATIONS.balloons),
|
|
sound: bool(n.sound, DEFAULT_NOTIFICATIONS.sound),
|
|
groups,
|
|
};
|
|
}
|