History

Remove unused prompt dialog infrastructure

The inline rename replaces the prompt dialog entirely, so remove the promptDialog.ts file, IPC channels, bridge methods, and related tests. Context menu improvements (destructive, position) are kept.

Julius Marminge committed 8 months ago

Commit cefe6411 parent 664451c6 files · +0 −295Browse files

Unified · Split

6 files
  1. apps/desktop/src/main.ts +0 −16
  2. apps/desktop/src/preload.ts +0 −2
  3. apps/desktop/src/promptDialog.ts +0 −231
  4. apps/web/src/wsNativeApi.test.ts +0 −34
  5. apps/web/src/wsNativeApi.ts +0 −10
  6. packages/contracts/src/ipc.ts +0 −2
apps/desktop/src/main.ts+0−16
@@ -10,13 +10,11 @@ import type { MenuItemConstructorOptions } from "electron"; · 9 unmodified lines
1010
1111 import { showDesktopConfirmDialog } from "./confirmDialog";
1212 import { fixPath } from "./fixPath";
13−import { showDesktopPromptDialog } from "./promptDialog";
1413
1514 fixPath();
1615
1716 const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
1817 const CONFIRM_CHANNEL = "desktop:confirm";
19−const PROMPT_CHANNEL = "desktop:prompt";
2018 const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
2119 const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
2220 const MENU_ACTION_CHANNEL = "desktop:menu-action";
@@ -436,20 +434,6 @@ function registerIpcHandlers(): void { · 413 unmodified lines
436434 return showDesktopConfirmDialog(message, owner);
437435 });
438436
439− ipcMain.removeHandler(PROMPT_CHANNEL);
440− ipcMain.handle(PROMPT_CHANNEL, async (_event, message: unknown, defaultValue: unknown) => {
441− if (typeof message !== "string") {
442− return null;
443− }
444−
445− const owner = BrowserWindow.getFocusedWindow() ?? mainWindow;
446− return showDesktopPromptDialog(
447− message,
448− typeof defaultValue === "string" ? defaultValue : "",
449− owner,
450− );
451− });
452−
453437 ipcMain.removeHandler(CONTEXT_MENU_CHANNEL);
454438 ipcMain.handle(
455439 CONTEXT_MENU_CHANNEL,
apps/desktop/src/preload.ts+0−2
@@ -3,7 +3,6 @@ import type { DesktopBridge } from "@t3tools/contracts"; · 2 unmodified lines
33
44 const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
55 const CONFIRM_CHANNEL = "desktop:confirm";
6−const PROMPT_CHANNEL = "desktop:prompt";
76 const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
87 const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
98 const MENU_ACTION_CHANNEL = "desktop:menu-action";
@@ -13,7 +12,6 @@ contextBridge.exposeInMainWorld("desktopBridge", { · 3 unmodified lines
1312 getWsUrl: () => wsUrl,
1413 pickFolder: () => ipcRenderer.invoke(PICK_FOLDER_CHANNEL),
1514 confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message),
16− prompt: (message, defaultValue) => ipcRenderer.invoke(PROMPT_CHANNEL, message, defaultValue),
1715 showContextMenu: (items, position) => ipcRenderer.invoke(CONTEXT_MENU_CHANNEL, items, position),
1816 openExternal: (url: string) => ipcRenderer.invoke(OPEN_EXTERNAL_CHANNEL, url),
1917 onMenuAction: (listener) => {
apps/desktop/src/promptDialog.ts+0−231
@@ -1,231 +0,0 @@
1−import { randomBytes } from "node:crypto";
2−import { BrowserWindow, ipcMain } from "electron";
3−
4−const PROMPT_WIDTH = 420;
5−const PROMPT_HEIGHT = 188;
6−
7−function createPromptHtml(input: {
8− message: string;
9− defaultValue: string;
10− submitChannel: string;
11− cancelChannel: string;
12−}): string {
13− const scriptConfig = JSON.stringify(input);
14− return `<!doctype html>
15−<html lang="en">
16− <head>
17− <meta charset="UTF-8" />
18− <meta name="viewport" content="width=device-width,initial-scale=1.0" />
19− <title>${input.message}</title>
20− <style>
21− :root {
22− color-scheme: light dark;
23− font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
24− --bg: #fff;
25− --fg: #1a1a1a;
26− --muted: #6b6b6b;
27− --border: #d4d4d4;
28− --input-bg: #fafafa;
29− --focus-ring: rgba(59, 130, 246, 0.5);
30− --btn-bg: #f5f5f5;
31− --btn-hover: #e8e8e8;
32− --btn-primary-bg: #171717;
33− --btn-primary-fg: #fff;
34− --btn-primary-hover: #333;
35− }
36− @media (prefers-color-scheme: dark) {
37− :root {
38− --bg: #1e1e1e;
39− --fg: #e4e4e4;
40− --muted: #999;
41− --border: #3a3a3a;
42− --input-bg: #2a2a2a;
43− --focus-ring: rgba(96, 165, 250, 0.5);
44− --btn-bg: #333;
45− --btn-hover: #404040;
46− --btn-primary-bg: #e4e4e4;
47− --btn-primary-fg: #1a1a1a;
48− --btn-primary-hover: #d0d0d0;
49− }
50− }
51− * { box-sizing: border-box; margin: 0; }
52− body {
53− padding: 16px;
54− background: var(--bg);
55− color: var(--fg);
56− -webkit-font-smoothing: antialiased;
57− }
58− p {
59− margin-bottom: 10px;
60− font-size: 13px;
61− font-weight: 500;
62− }
63− input {
64− width: 100%;
65− padding: 7px 10px;
66− font-size: 13px;
67− font-family: inherit;
68− color: var(--fg);
69− background: var(--input-bg);
70− border: 1px solid var(--border);
71− border-radius: 6px;
72− outline: none;
73− transition: border-color 0.15s, box-shadow 0.15s;
74− }
75− input:focus {
76− border-color: var(--focus-ring);
77− box-shadow: 0 0 0 2px var(--focus-ring);
78− }
79− .buttons {
80− display: flex;
81− justify-content: flex-end;
82− gap: 8px;
83− margin-top: 14px;
84− }
85− button {
86− min-width: 72px;
87− padding: 6px 14px;
88− font-size: 12px;
89− font-family: inherit;
90− font-weight: 500;
91− border: 1px solid var(--border);
92− border-radius: 6px;
93− cursor: pointer;
94− background: var(--btn-bg);
95− color: var(--fg);
96− transition: background 0.15s;
97− }
98− button:hover { background: var(--btn-hover); }
99− button:active { opacity: 0.85; }
100− button[type="submit"] {
101− background: var(--btn-primary-bg);
102− color: var(--btn-primary-fg);
103− border-color: var(--btn-primary-bg);
104− }
105− button[type="submit"]:hover { background: var(--btn-primary-hover); }
106− </style>
107− </head>
108− <body>
109− <p id="message"></p>
110− <form id="form">
111− <input id="value" autocomplete="off" />
112− <div class="buttons">
113− <button id="cancel" type="button">Cancel</button>
114− <button type="submit">Save</button>
115− </div>
116− </form>
117− <script>
118− const { ipcRenderer } = require("electron");
119− const config = ${scriptConfig};
120− const message = document.getElementById("message");
121− const form = document.getElementById("form");
122− const valueInput = document.getElementById("value");
123− const cancelButton = document.getElementById("cancel");
124−
125− message.textContent = config.message;
126− valueInput.value = config.defaultValue;
127−
128− function cancel() {
129− ipcRenderer.send(config.cancelChannel);
130− window.close();
131− }
132−
133− form.addEventListener("submit", (event) => {
134− event.preventDefault();
135− ipcRenderer.send(config.submitChannel, valueInput.value);
136− window.close();
137− });
138− cancelButton.addEventListener("click", cancel);
139− window.addEventListener("keydown", (event) => {
140− if (event.key === "Escape") {
141− event.preventDefault();
142− cancel();
143− }
144− });
145− window.addEventListener("DOMContentLoaded", () => {
146− valueInput.focus();
147− valueInput.select();
148− });
149− </script>
150− </body>
151−</html>`;
152−}
153−
154−export async function showDesktopPromptDialog(
155− message: string,
156− defaultValue: string,
157− ownerWindow: BrowserWindow | null,
158−): Promise<string | null> {
159− const normalizedMessage = message.trim();
160− if (normalizedMessage.length === 0) {
161− return null;
162− }
163−
164− const token = randomBytes(12).toString("hex");
165− const submitChannel = `desktop:prompt-dialog:submit:${token}`;
166− const cancelChannel = `desktop:prompt-dialog:cancel:${token}`;
167−
168− const promptWindow = new BrowserWindow({
169− width: PROMPT_WIDTH,
170− height: PROMPT_HEIGHT,
171− useContentSize: true,
172− show: false,
173− resizable: false,
174− minimizable: false,
175− maximizable: false,
176− fullscreenable: false,
177− autoHideMenuBar: true,
178− title: normalizedMessage,
179− ...(ownerWindow ? { parent: ownerWindow } : {}),
180− modal: ownerWindow !== null,
181− webPreferences: {
182− nodeIntegration: true,
183− contextIsolation: false,
184− },
185− });
186− promptWindow.removeMenu();
187−
188− return new Promise<string | null>((resolve) => {
189− let settled = false;
190−
191− const settle = (value: string | null) => {
192− if (settled) return;
193− settled = true;
194− cleanup();
195− resolve(value);
196− };
197−
198− const cleanup = () => {
199− ipcMain.removeAllListeners(submitChannel);
200− ipcMain.removeAllListeners(cancelChannel);
201− if (!promptWindow.isDestroyed()) {
202− promptWindow.destroy();
203− }
204− };
205−
206− ipcMain.once(submitChannel, (_event, value: unknown) => {
207− if (typeof value !== "string") {
208− settle(null);
209− return;
210− }
211− settle(value);
212− });
213− ipcMain.once(cancelChannel, () => settle(null));
214− promptWindow.once("closed", () => settle(null));
215−
216− const html = createPromptHtml({
217− message: normalizedMessage,
218− defaultValue,
219− submitChannel,
220− cancelChannel,
221− });
222− void promptWindow
223− .loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`)
224− .then(() => {
225− if (!promptWindow.isDestroyed()) {
226− promptWindow.show();
227− }
228− })
229− .catch(() => settle(null));
230− });
231−}
apps/web/src/wsNativeApi.test.ts+0−34
@@ -68,7 +68,6 @@ beforeEach(() => { · 67 unmodified lines
6868 subscribeMock.mockClear();
6969 channelListeners.clear();
7070 Reflect.deleteProperty(getWindowForTest(), "desktopBridge");
71− Reflect.deleteProperty(getWindowForTest(), "prompt");
7271 });
7372
7473 afterEach(() => {
@@ -377,37 +376,4 @@ describe("wsNativeApi", () => { · 302 unmodified lines
377376 );
378377 });
379378
380− it("forwards prompt calls to desktop bridge", async () => {
381− const prompt = vi.fn().mockResolvedValue("renamed");
382− Object.defineProperty(getWindowForTest(), "desktopBridge", {
383− configurable: true,
384− writable: true,
385− value: {
386− prompt,
387− },
388− });
389−
390− const { createWsNativeApi } = await import("./wsNativeApi");
391− const api = createWsNativeApi();
392− const value = await api.dialogs.prompt("Rename thread", "Old title");
393−
394− expect(value).toBe("renamed");
395− expect(prompt).toHaveBeenCalledWith("Rename thread", "Old title");
396− });
397−
398− it("uses browser prompt fallback when desktop bridge is unavailable", async () => {
399− const prompt = vi.fn().mockReturnValue("renamed");
400− Object.defineProperty(getWindowForTest(), "prompt", {
401− configurable: true,
402− writable: true,
403− value: prompt,
404− });
405−
406− const { createWsNativeApi } = await import("./wsNativeApi");
407− const api = createWsNativeApi();
408− const value = await api.dialogs.prompt("Rename thread", "Old title");
409−
410− expect(value).toBe("renamed");
411− expect(prompt).toHaveBeenCalledWith("Rename thread", "Old title");
412− });
413379 });
apps/web/src/wsNativeApi.ts+0−10
@@ -124,16 +124,6 @@ export function createWsNativeApi(): NativeApi { · 123 unmodified lines
124124 }
125125 return window.confirm(message);
126126 },
127− prompt: async (message, defaultValue) => {
128− if (window.desktopBridge) {
129− return window.desktopBridge.prompt(message, defaultValue);
130− }
131− try {
132− return window.prompt(message, defaultValue ?? "");
133− } catch {
134− return null;
135− }
136− },
137127 },
138128 terminal: {
139129 open: (input) => transport.request(WS_METHODS.terminalOpen, input),
packages/contracts/src/ipc.ts+0−2
@@ -47,7 +47,6 @@ export interface DesktopBridge { · 46 unmodified lines
4747 getWsUrl: () => string | null;
4848 pickFolder: () => Promise<string | null>;
4949 confirm: (message: string) => Promise<boolean>;
50− prompt: (message: string, defaultValue?: string) => Promise<string | null>;
5150 showContextMenu: <T extends string>(
5251 items: readonly ContextMenuItem<T>[],
5352 position?: { x: number; y: number },
@@ -60,7 +59,6 @@ export interface NativeApi { · 6 unmodified lines
6059 dialogs: {
6160 pickFolder: () => Promise<string | null>;
6261 confirm: (message: string) => Promise<boolean>;
63− prompt: (message: string, defaultValue?: string) => Promise<string | null>;
6462 };
6563 terminal: {
6664 open: (input: TerminalOpenInput) => Promise<TerminalSessionSnapshot>;