Add streaming terminal panel, status bar, and toast notifications
Step 3 of the build plan. Long-running ddev commands (start/stop/ restart) now spawn via commandRunner.ts and stream stdout/stderr to the renderer over IPC (terminal:data/terminal:exit), instead of the previous fire-and-forget JSON exec. A single useTerminalEvents hook owns turning those events into terminal panel lines, status bar progress, and success/error toasts, decoupled from whichever mutation triggered the command so later features (snapshots, addons) can reuse the same pipeline. Cancel is wired end-to-end: the status bar's Cancel button kills the underlying child process via a tracked operation id. Verified against the real scratch DDEV project via a CDP driver script (Electron launched with --remoteDebuggingPort, driven by clicking real DOM buttons): confirmed live streaming output, the status bar's spinner/cancel affordance, and that cancelling mid- restart genuinely interrupts the process rather than just hiding the UI (only the first output line appears, vs. full output on a normal run).
This commit is contained in:
+27
-5
@@ -1,6 +1,11 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
import type { DdevProjectDetail, DdevProjectSummary } from '../shared/types'
|
||||
import type {
|
||||
DdevProjectDetail,
|
||||
DdevProjectSummary,
|
||||
TerminalDataEvent,
|
||||
TerminalExitEvent
|
||||
} from '../shared/types'
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
@@ -8,9 +13,26 @@ const api = {
|
||||
list: (): Promise<DdevProjectSummary[]> => ipcRenderer.invoke('projects:list'),
|
||||
describe: (name: string): Promise<DdevProjectDetail> =>
|
||||
ipcRenderer.invoke('projects:describe', name),
|
||||
start: (name: string): Promise<void> => ipcRenderer.invoke('projects:start', name),
|
||||
stop: (name: string): Promise<void> => ipcRenderer.invoke('projects:stop', name),
|
||||
restart: (name: string): Promise<void> => ipcRenderer.invoke('projects:restart', name)
|
||||
start: (operationId: string, name: string): Promise<void> =>
|
||||
ipcRenderer.invoke('projects:start', operationId, name),
|
||||
stop: (operationId: string, name: string): Promise<void> =>
|
||||
ipcRenderer.invoke('projects:stop', operationId, name),
|
||||
restart: (operationId: string, name: string): Promise<void> =>
|
||||
ipcRenderer.invoke('projects:restart', operationId, name)
|
||||
},
|
||||
terminal: {
|
||||
cancel: (operationId: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke('terminal:cancel', operationId),
|
||||
onData: (callback: (event: TerminalDataEvent) => void): (() => void) => {
|
||||
const listener = (_event: IpcRendererEvent, data: TerminalDataEvent): void => callback(data)
|
||||
ipcRenderer.on('terminal:data', listener)
|
||||
return () => ipcRenderer.removeListener('terminal:data', listener)
|
||||
},
|
||||
onExit: (callback: (event: TerminalExitEvent) => void): (() => void) => {
|
||||
const listener = (_event: IpcRendererEvent, data: TerminalExitEvent): void => callback(data)
|
||||
ipcRenderer.on('terminal:exit', listener)
|
||||
return () => ipcRenderer.removeListener('terminal:exit', listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user