返回 DeepSeek-Reasonix
dialogs.ts
根目录 / desktop / electron / src / main / dialogs.ts
1 import type { BrowserWindow, Dialog, MessageBoxOptions, OpenDialogOptions, SaveDialogOptions } from "electron";
2 import { join } from "node:path";
3 import { bool, str, strList, type Params } from "./params.js";
4
5 export interface DialogFilter {
6 name: string;
7 extensions: string[];
8 }
9
10 // Wails filters carry `*.png;*.jpg` patterns; Electron wants bare extensions.
11 export function fileFiltersFromPatterns(value: unknown): DialogFilter[] {
12 if (!Array.isArray(value)) return [];
13 const filters: DialogFilter[] = [];
14 for (const entry of value) {
15 if (typeof entry !== "object" || entry === null) continue;
16 const { displayName, pattern } = entry as { displayName?: unknown; pattern?: unknown };
17 if (typeof pattern !== "string") continue;
18 const extensions = pattern
19 .split(";")
20 .map((part) => part.trim())
21 .filter((part) => part !== "")
22 .map((part) => (part === "*" || part === "*.*" ? "*" : part.replace(/^\*?\./, "")))
23 .filter((part) => part !== "");
24 if (extensions.length === 0) continue;
25 filters.push({ name: typeof displayName === "string" && displayName !== "" ? displayName : pattern, extensions });
26 }
27 return filters;
28 }
29
30 export type DialogApi = Pick<Dialog, "showOpenDialog" | "showSaveDialog" | "showMessageBox">;
31
32 type MessageType = "info" | "warning" | "error" | "question";
33
34 function messageType(value: string): MessageType {
35 return value === "warning" || value === "error" || value === "question" ? value : "info";
36 }
37
38 export class DialogHost {
39 constructor(private readonly dialog: DialogApi, private readonly parent: () => BrowserWindow | undefined) {}
40
41 private open(options: OpenDialogOptions) {
42 const parent = this.parent();
43 return parent ? this.dialog.showOpenDialog(parent, options) : this.dialog.showOpenDialog(options);
44 }
45
46 private save(options: SaveDialogOptions) {
47 const parent = this.parent();
48 return parent ? this.dialog.showSaveDialog(parent, options) : this.dialog.showSaveDialog(options);
49 }
50
51 private box(options: MessageBoxOptions) {
52 const parent = this.parent();
53 return parent ? this.dialog.showMessageBox(parent, options) : this.dialog.showMessageBox(options);
54 }
55
56 async openDirectory(params: Params): Promise<{ path: string }> {
57 const result = await this.open({
58 title: str(params, "title") || undefined,
59 defaultPath: str(params, "defaultDirectory") || undefined,
60 properties: ["openDirectory", "createDirectory"],
61 });
62 return { path: result.canceled ? "" : (result.filePaths[0] ?? "") };
63 }
64
65 async openFile(params: Params): Promise<{ paths: string[] }> {
66 const result = await this.open({
67 title: str(params, "title") || undefined,
68 defaultPath: str(params, "defaultDirectory") || undefined,
69 filters: fileFiltersFromPatterns(params.filters),
70 properties: bool(params, "multiple") ? ["openFile", "multiSelections"] : ["openFile"],
71 });
72 return { paths: result.canceled ? [] : result.filePaths };
73 }
74
75 async saveFile(params: Params): Promise<{ path: string }> {
76 const directory = str(params, "defaultDirectory");
77 const filename = str(params, "defaultFilename");
78 const defaultPath = directory && filename ? join(directory, filename) : directory || filename || undefined;
79 const result = await this.save({
80 title: str(params, "title") || undefined,
81 defaultPath,
82 filters: fileFiltersFromPatterns(params.filters),
83 });
84 return { path: result.canceled ? "" : (result.filePath ?? "") };
85 }
86
87 async message(params: Params): Promise<{ button: string }> {
88 const buttons = strList(params, "buttons");
89 const labels = buttons.length > 0 ? buttons : ["OK"];
90 const defaultIndex = labels.indexOf(str(params, "defaultButton"));
91 const cancelIndex = labels.indexOf(str(params, "cancelButton"));
92 const result = await this.box({
93 type: messageType(str(params, "type")),
94 title: str(params, "title"),
95 message: str(params, "message"),
96 buttons: labels,
97 defaultId: defaultIndex >= 0 ? defaultIndex : 0,
98 cancelId: cancelIndex >= 0 ? cancelIndex : -1,
99 noLink: true,
100 });
101 return { button: labels[result.response] ?? "" };
102 }
103 }
104
104 lines TYPESCRIPT