Editor opens in native windows (one per file)
Real BrowserWindow per edited file with its own RPC instance, deduped by sessionId+path. The same Angular bundle boots an editor-only root via preload globals. Native close isn't cancellable in electrobun, so the editor mirrors its draft to bun; traffic-light closes get a native Save/Discard prompt after the fact, and failed saves park the draft in the edit-leftovers dir. In-window closes (Cmd+W, Close) use a styled prompt; the quit guard now also counts unsaved editors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+63
-2
@@ -1,6 +1,6 @@
|
||||
import { BrowserView, Utils } from "electrobun/bun";
|
||||
import type { AppRPC } from "../shared/rpc";
|
||||
import { effectiveConcurrency, type Result } from "../shared/types";
|
||||
import type { AppRPC, EditorOpenContext } from "../shared/rpc";
|
||||
import { effectiveConcurrency, type AppSettings, type Result } from "../shared/types";
|
||||
import type { PanelContextMenu } from "./context-menu";
|
||||
import { readFileText, writeFileText, type EditManager } from "./edits";
|
||||
import type { SessionRegistry } from "./fs/registry";
|
||||
@@ -27,6 +27,8 @@ export interface AppDeps {
|
||||
events: {
|
||||
sessionClosed: (info: { sessionId: string; reason: string }) => void;
|
||||
connectionsChanged: () => void;
|
||||
openEditor: (ctx: EditorOpenContext) => void;
|
||||
settingsChanged: (settings: AppSettings) => void;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -195,6 +197,7 @@ export function createAppRPC({
|
||||
wrap(async () => {
|
||||
await settings.save(next);
|
||||
engine.setConcurrency(effectiveConcurrency(next));
|
||||
events.settingsChanged(next);
|
||||
return {};
|
||||
}),
|
||||
|
||||
@@ -261,6 +264,64 @@ export function createAppRPC({
|
||||
Utils.moveToTrash(path);
|
||||
return {};
|
||||
}),
|
||||
|
||||
// ---- editor windows ----
|
||||
openEditor: (ctx) =>
|
||||
wrap(async () => {
|
||||
events.openEditor(ctx);
|
||||
return {};
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export interface EditorRpcHooks {
|
||||
ctx: EditorOpenContext;
|
||||
/** Draft mirror tick: store dirty flag + content on the window record. */
|
||||
onDraft: (state: { dirty: boolean; content: string }) => void;
|
||||
/** editorSave succeeded: clean the record, retitle, refresh main panels. */
|
||||
onSaved: () => void;
|
||||
/** Webview resolved its dirty prompt: close without the native fallback. */
|
||||
onCloseConfirmed: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC for one editor window. Each window MUST get its own instance (binding a
|
||||
* BrowserView steals the rpc's transport), and the hooks close over that
|
||||
* window's record — no ids on the wire.
|
||||
*/
|
||||
export function createEditorRPC(
|
||||
deps: { registry: SessionRegistry; settings: SettingsStore },
|
||||
hooks: EditorRpcHooks,
|
||||
) {
|
||||
return BrowserView.defineRPC<AppRPC>({
|
||||
maxRequestTime: MAX_REQUEST_TIME_MS,
|
||||
handlers: {
|
||||
requests: {
|
||||
readFile: ({ sessionId, path }) =>
|
||||
wrap(async () => ({
|
||||
content: await readFileText(deps.registry.get(sessionId), path),
|
||||
})),
|
||||
|
||||
getSettings: () => wrap(async () => ({ settings: await deps.settings.load() })),
|
||||
|
||||
editorSave: ({ content }) =>
|
||||
wrap(async () => {
|
||||
const { sessionId, path } = hooks.ctx;
|
||||
await writeFileText(deps.registry.get(sessionId), path, content);
|
||||
hooks.onSaved();
|
||||
return {};
|
||||
}),
|
||||
|
||||
editorClose: () =>
|
||||
wrap(async () => {
|
||||
hooks.onCloseConfirmed();
|
||||
return {};
|
||||
}),
|
||||
},
|
||||
messages: {
|
||||
editorState: (state) => hooks.onDraft(state),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user