返回 DeepSeek-Reasonix
zoomStore.ts
根目录 / desktop / electron / src / main / zoomStore.ts
1 import { rename, readFile, writeFile, mkdir } from "node:fs/promises";
2 import { dirname, join } from "node:path";
3
4 export const MIN_APP_ZOOM = 0.5;
5 export const MAX_APP_ZOOM = 2;
6 export const APP_ZOOM_STEP = 0.05;
7 export type AppZoomSource = "default" | "user" | "migrated";
8 export interface AppZoomState { version: 1; appZoomFactor: number; source: AppZoomSource; }
9
10 const DEFAULT: AppZoomState = { version: 1, appZoomFactor: 1, source: "default" };
11
12 export function normalizeAppZoom(value: number): number {
13 if (!Number.isFinite(value)) return 1;
14 const clamped = Math.min(MAX_APP_ZOOM, Math.max(MIN_APP_ZOOM, value));
15 return Number((MIN_APP_ZOOM + Math.round((clamped - MIN_APP_ZOOM) / APP_ZOOM_STEP) * APP_ZOOM_STEP).toFixed(2));
16 }
17
18 export class AppZoomStore {
19 private state: AppZoomState = DEFAULT;
20 constructor(private readonly file: string, private readonly legacyFile?: string) {}
21 async load(): Promise<AppZoomState> {
22 try {
23 const parsed = JSON.parse(await readFile(this.file, "utf8")) as Partial<AppZoomState>;
24 if (parsed.version === 1 && typeof parsed.appZoomFactor === "number" && Number.isFinite(parsed.appZoomFactor)
25 && (parsed.source === "default" || parsed.source === "user" || parsed.source === "migrated")) {
26 this.state = { version: 1, appZoomFactor: normalizeAppZoom(parsed.appZoomFactor), source: parsed.source };
27 return this.state;
28 }
29 } catch { /* first launch or corrupt file */ }
30 this.state = await this.migrate();
31 await this.persist(this.state);
32 return this.state;
33 }
34 async set(factor: number, source: AppZoomSource = "user"): Promise<AppZoomState> {
35 if (!Number.isFinite(factor)) throw new Error("app zoom factor must be finite");
36 this.state = { version: 1, appZoomFactor: normalizeAppZoom(factor), source };
37 await this.persist(this.state);
38 return this.state;
39 }
40 async reset(): Promise<AppZoomState> { return this.set(1, "user"); }
41 get current(): AppZoomState { return this.state; }
42 private async migrate(): Promise<AppZoomState> {
43 if (!this.legacyFile) return DEFAULT;
44 try {
45 const parsed = JSON.parse(await readFile(this.legacyFile, "utf8")) as { zoomFactor?: unknown; source?: unknown };
46 // Legacy files had no trustworthy source marker; do not inherit an accidental 70% value.
47 if (parsed.source === "user" && typeof parsed.zoomFactor === "number" && Number.isFinite(parsed.zoomFactor)) {
48 return { version: 1, appZoomFactor: normalizeAppZoom(parsed.zoomFactor), source: "migrated" };
49 }
50 } catch { /* use default */ }
51 return DEFAULT;
52 }
53 private async persist(state: AppZoomState): Promise<void> {
54 await mkdir(dirname(this.file), { recursive: true });
55 const temp = join(dirname(this.file), `.${state.version}.zoom.tmp`);
56 await writeFile(temp, `${JSON.stringify(state)}\n`, "utf8");
57 await rename(temp, this.file);
58 }
59 }
60
60 lines TYPESCRIPT