main retired electrobun's native context menu (a documented no-op on Linux) in favour of an HTML one on every platform. Merging that here needs the menu to look right under the Win7 skin as well. Conflicts, all resolved by keeping both sides: - src/bun/index.ts — createAppRPC keeps the branch's mainWindow getter, drops main's removed contextMenu dep. - ui/src/app/app.ts — imports both TitleBar and ContextMenu. - ui/src/app/platform.ts — kept the branch's version (its IS_MAC_CHROME accounts for the frameless win7 chrome, and MOD_LABEL correctly keys on the real platform rather than the chrome), plus exporting IS_MAC and adding IS_LINUX for the menu's per-OS shell wording. Aero menu (branch-only): 7.css has no menu component, so it is hand-authored across both layers — the row <button>s shed their push-button slab in `skin` (revert-layer only reaches `utilities` from there), and the square-cornered slab, hairline border and gradient selection band are stated in `skinfix`. Built strictly from existing --w7-* vars, never literal hex, so the hand-written dark Win7 palette recolours it for free. Hover and the keyboard cursor share one band (the component marks the cursor with `bg-accent`, which is overridden alongside :hover) as a real Win7 menu does. Also answers a question the merge raised: frameless windows DO work on Linux. The GTK wrapper honours titleBarStyle "hidden" — it calls gtk_window_set_decorated(false) and logs "GTK: Created window without decorations (custom titlebar)". Verified on GNOME/Wayland and documented in architecture.md. Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (492.52 kB initial, still under the 500 kB budget), app launches with the skin active, frameless, no console errors and no GBM failures. Co-Authored-By: Claude <noreply@anthropic.com>
223 lines
8.5 KiB
TypeScript
223 lines
8.5 KiB
TypeScript
import type {
|
|
AppSettings,
|
|
ConnectionConfig,
|
|
ConnectionsFile,
|
|
EntryType,
|
|
FileEntry,
|
|
Result,
|
|
SavedConnection,
|
|
SshConfigHost,
|
|
Stack,
|
|
TransferSnapshot,
|
|
} from "./types";
|
|
|
|
/** Everything an editor window needs to know about the file it hosts. */
|
|
export interface EditorOpenContext {
|
|
sessionId: string;
|
|
path: string;
|
|
name: string;
|
|
sessionTitle: string;
|
|
}
|
|
|
|
// Structurally equivalent to electrobun's ElectrobunRPCSchema, written in
|
|
// resolved form so this file stays importable from the Angular bundle without
|
|
// pulling in bun-only type modules.
|
|
//
|
|
// bun.requests = calls the webview makes, handled in src/bun/rpc-handlers.ts
|
|
// webview.messages = push events from bun, handled in ui rpc.service.ts
|
|
//
|
|
// ONE schema serves both window kinds (main + editor): electrobun allows
|
|
// partial handler sets, and messages nobody listens to are silently dropped,
|
|
// so each side only implements/hears what applies to it.
|
|
export type AppRPC = {
|
|
bun: {
|
|
requests: {
|
|
// sessions
|
|
connect: {
|
|
params: { config: ConnectionConfig };
|
|
response: Result<{ sessionId: string; homeDir: string; fsKey: string }>;
|
|
};
|
|
disconnect: { params: { sessionId: string }; response: Result };
|
|
// filesystem (uniform across "local" and sftp sessions)
|
|
listDir: {
|
|
params: { sessionId: string; path: string | null }; // null -> home dir
|
|
response: Result<{ path: string; entries: FileEntry[] }>;
|
|
};
|
|
// Local dirs to fs.watch for auto-refresh (the active tab's local panels);
|
|
// a change pushes pathRefresh. Remote panels are polled UI-side instead.
|
|
setWatchedDirs: { params: { dirs: string[] }; response: Result };
|
|
mkdir: { params: { sessionId: string; path: string }; response: Result };
|
|
createFile: { params: { sessionId: string; path: string }; response: Result };
|
|
rename: {
|
|
params: { sessionId: string; oldPath: string; newPath: string };
|
|
response: Result;
|
|
};
|
|
deleteEntries: {
|
|
params: { sessionId: string; entries: { path: string; type: EntryType }[] };
|
|
response: Result;
|
|
};
|
|
chmod: {
|
|
params: { sessionId: string; paths: string[]; mode: number };
|
|
response: Result;
|
|
};
|
|
// transfers
|
|
enqueueTransfers: {
|
|
params: {
|
|
srcSessionId: string;
|
|
items: { path: string; type: EntryType }[];
|
|
dstSessionId: string;
|
|
dstDir: string;
|
|
};
|
|
response: Result<{ jobIds: string[] }>;
|
|
};
|
|
cancelTransfer: { params: { jobId: string }; response: Result };
|
|
dismissTransfer: { params: { jobId: string }; response: Result };
|
|
// connection store
|
|
getConnections: {
|
|
params: Record<string, never>;
|
|
response: Result<{ store: ConnectionsFile }>;
|
|
};
|
|
upsertConnection: {
|
|
params: { connection: SavedConnection };
|
|
response: Result<{ id: string }>;
|
|
};
|
|
deleteConnection: { params: { id: string }; response: Result };
|
|
upsertStack: { params: { stack: Stack }; response: Result<{ id: string }> };
|
|
deleteStack: { params: { id: string }; response: Result };
|
|
/** Native file picker + parse of a WinSCP.ini export into the store. */
|
|
importWinScp: {
|
|
params: Record<string, never>;
|
|
response: Result<{ connections: number; stacks: number; skipped: number }>;
|
|
};
|
|
getSshConfigHosts: {
|
|
params: Record<string, never>;
|
|
response: Result<{ hosts: SshConfigHost[] }>;
|
|
};
|
|
// settings
|
|
getSettings: {
|
|
params: Record<string, never>;
|
|
response: Result<{ settings: AppSettings }>;
|
|
};
|
|
saveSettings: { params: { settings: AppSettings }; response: Result };
|
|
// file editing
|
|
readFile: {
|
|
params: { sessionId: string; path: string };
|
|
response: Result<{ content: string }>;
|
|
};
|
|
writeFile: {
|
|
params: { sessionId: string; path: string; content: string };
|
|
response: Result;
|
|
};
|
|
editExternal: {
|
|
params: { sessionId: string; path: string };
|
|
response: Result<{ watching: boolean }>;
|
|
};
|
|
pickEditorApp: {
|
|
params: Record<string, never>;
|
|
response: Result<{ path: string | null }>;
|
|
};
|
|
pickKeyFile: {
|
|
params: Record<string, never>;
|
|
response: Result<{ path: string | null }>;
|
|
};
|
|
getEditLeftovers: {
|
|
params: Record<string, never>;
|
|
response: Result<{ names: string[] }>;
|
|
};
|
|
clearEditLeftovers: { params: Record<string, never>; response: Result };
|
|
// local shell integration
|
|
/**
|
|
* Shell actions the context menu triggers on a LOCAL path. The menu
|
|
* itself is HTML (ui/src/app/context-menu.ts) — electrobun's native
|
|
* context menu is a no-op on Linux — but these three still need the
|
|
* bun side's Utils.
|
|
*/
|
|
shellItemAction: {
|
|
params: { action: "reveal" | "open" | "copyPath"; path: string };
|
|
response: Result;
|
|
};
|
|
moveToTrash: { params: { path: string }; response: Result };
|
|
/** Native About box (name, Build n, commit, engine versions). */
|
|
showAbout: { params: Record<string, never>; response: Result };
|
|
/** Windows drive roots for the local panel ("C:\", …); empty elsewhere. */
|
|
listDrives: { params: Record<string, never>; response: Result<{ drives: string[] }> };
|
|
// editor windows
|
|
/** Main window only: open (or focus) a native editor window. */
|
|
openEditor: { params: EditorOpenContext; response: Result };
|
|
/** Editor windows only — handled by that window's own rpc closure. */
|
|
editorSave: { params: { content: string }; response: Result };
|
|
editorClose: { params: Record<string, never>; response: Result };
|
|
// connection-manager window
|
|
/** Main window: open (or focus) the single connection-manager window. */
|
|
openConnectionManager: { params: Record<string, never>; response: Result };
|
|
/** Manager window only: connect/open-stack run in the MAIN window. */
|
|
managerConnect: { params: { id: string }; response: Result };
|
|
managerOpenStack: { params: { id: string }; response: Result };
|
|
managerClose: { params: Record<string, never>; response: Result };
|
|
// custom window chrome (win7 skin — the window is frameless, so the
|
|
// webview's title bar owns the controls). Served by every window kind.
|
|
windowMinimize: { params: Record<string, never>; response: Result };
|
|
/** Maximize/restore in one call; responds with the resulting state. */
|
|
windowToggleMaximize: {
|
|
params: Record<string, never>;
|
|
response: Result<{ maximized: boolean }>;
|
|
};
|
|
windowClose: { params: Record<string, never>; response: Result };
|
|
/** First paint: the title bar needs the state before any resize fires. */
|
|
windowIsMaximized: {
|
|
params: Record<string, never>;
|
|
response: Result<{ maximized: boolean }>;
|
|
};
|
|
// auto-updater (electrobun Updater)
|
|
/** Main window: re-run the update check on demand (menu item). */
|
|
checkForUpdate: {
|
|
params: Record<string, never>;
|
|
response: Result<{ updateAvailable: boolean; version: string }>;
|
|
};
|
|
/** Download the pending update (patch-chain, else full). Progress arrives via updateStatus. */
|
|
downloadUpdate: { params: Record<string, never>; response: Result };
|
|
/** Swap in the downloaded update and relaunch — quits the app, so may not resolve. */
|
|
applyUpdate: { params: Record<string, never>; response: Result };
|
|
};
|
|
messages: {
|
|
/** Editor → bun draft mirror; safety net for the non-cancellable native close. */
|
|
editorState: { dirty: boolean; content: string };
|
|
};
|
|
};
|
|
webview: {
|
|
requests: Record<never, never>;
|
|
messages: {
|
|
transferUpdate: TransferSnapshot;
|
|
sessionClosed: { sessionId: string; reason: string };
|
|
menuAction: { action: string };
|
|
/** bun → editor windows when the main window saves settings. */
|
|
settingsChanged: { settings: AppSettings };
|
|
/** bun → main after an editor save, so matching panels refresh. */
|
|
pathRefresh: { sessionId: string; dir: string };
|
|
/** bun → one editor: run the in-window close flow (dirty prompt). */
|
|
editorCloseRequest: Record<string, never>;
|
|
/** bun → main: the modal connection manager opened/closed. */
|
|
managerState: { open: boolean };
|
|
/**
|
|
* bun → any window: maximize state changed, so the custom title bar can
|
|
* swap its maximize/restore glyph. electrobun has no maximize event —
|
|
* this is derived from the window's `resize` event (see windows.ts
|
|
* trackMaximizeState), which also covers OS-initiated Aero Snap.
|
|
*/
|
|
windowState: { maximized: boolean };
|
|
/** bun → main: the manager window changed the connection store. */
|
|
connectionsChanged: Record<string, never>;
|
|
/**
|
|
* bun → main: auto-updater lifecycle, drives the tab-bar update pill.
|
|
* "ready" is not a phase — the UI derives it from downloadUpdate resolving ok.
|
|
*/
|
|
updateStatus: {
|
|
phase: "available" | "downloading" | "error";
|
|
version?: string;
|
|
progress?: number;
|
|
error?: string;
|
|
};
|
|
};
|
|
};
|
|
};
|