macOS application menu
Standard menus (App: hide/quit; Edit: clipboard roles enabling Cmd+C/V/X/Z; View: Refresh Cmd+R + fullscreen; Window: minimize/zoom/cycle) plus File with New/Close Tab (Cmd+T/W), New Folder/File, and a Connect submenu built from saved connections (folders as nested submenus, Connection Manager entry). Custom actions forward to the webview as menuAction messages; the menu rebuilds whenever connections change. macOS-only per electrobun support. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,13 @@ import { Component, inject } from '@angular/core';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { BRANDING } from '@shared/branding';
|
||||
import { ConnectionManager } from './connection-manager/connection-manager';
|
||||
import { ConnectionsService } from './connections.service';
|
||||
import { ChmodDialog } from './dialogs/chmod-dialog';
|
||||
import { ConfirmDialog } from './dialogs/confirm-dialog';
|
||||
import { OverwriteDialog } from './dialogs/overwrite-dialog';
|
||||
import { PromptDialog } from './dialogs/prompt-dialog';
|
||||
import { DialogsService } from './dialogs.service';
|
||||
import { FileActionsService } from './file-actions.service';
|
||||
import { FilePanel } from './file-panel/file-panel';
|
||||
import { PanelsService } from './panels.service';
|
||||
import { RpcService } from './rpc.service';
|
||||
@@ -37,9 +39,46 @@ export class App {
|
||||
readonly toast = inject(ToastService);
|
||||
readonly dialogs = inject(DialogsService);
|
||||
readonly panels = inject(PanelsService);
|
||||
readonly actions = inject(FileActionsService);
|
||||
readonly connections = inject(ConnectionsService);
|
||||
|
||||
constructor() {
|
||||
document.title = BRANDING.appName;
|
||||
if (this.rpc.available) {
|
||||
this.rpc.onMenuAction((action) => this.handleMenuAction(action));
|
||||
}
|
||||
}
|
||||
|
||||
/** Native application-menu actions forwarded from the bun process. */
|
||||
private handleMenuAction(action: string): void {
|
||||
if (action.startsWith('connect:')) {
|
||||
const id = action.slice('connect:'.length);
|
||||
const connection = this.connections.connections().find((c) => c.id === id);
|
||||
if (connection) {
|
||||
void this.panels.connectPanel(this.panels.activeSide(), connection, connection.name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (action) {
|
||||
case 'new-tab':
|
||||
this.panels.newWorkspace();
|
||||
break;
|
||||
case 'close-tab':
|
||||
this.panels.closeWorkspace(this.panels.activeWorkspaceId());
|
||||
break;
|
||||
case 'new-folder':
|
||||
this.actions.mkdir(this.panels.active());
|
||||
break;
|
||||
case 'new-file':
|
||||
this.actions.createFile(this.panels.active());
|
||||
break;
|
||||
case 'refresh-active':
|
||||
void this.panels.active().refresh();
|
||||
break;
|
||||
case 'manage-connections':
|
||||
this.dialogs.manager.set(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Tab (workspace) shortcuts — panel-level keys live on the panels themselves. */
|
||||
|
||||
@@ -18,6 +18,7 @@ export class RpcService {
|
||||
// (this froze the app), and signal coalescing would drop progress snapshots.
|
||||
private transferListeners = new Set<(s: TransferSnapshot) => void>();
|
||||
private closedListeners = new Set<(e: SessionClosedEvent) => void>();
|
||||
private menuListeners = new Set<(action: string) => void>();
|
||||
|
||||
private rpc: ReturnType<typeof Electroview.defineRPC<AppRPC>> | undefined;
|
||||
private ready: Promise<void> = Promise.resolve();
|
||||
@@ -36,6 +37,9 @@ export class RpcService {
|
||||
sessionClosed: (event) => {
|
||||
for (const listener of this.closedListeners) listener(event);
|
||||
},
|
||||
menuAction: ({ action }) => {
|
||||
for (const listener of this.menuListeners) listener(action);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -60,6 +64,10 @@ export class RpcService {
|
||||
this.closedListeners.add(listener);
|
||||
}
|
||||
|
||||
onMenuAction(listener: (action: string) => void): void {
|
||||
this.menuListeners.add(listener);
|
||||
}
|
||||
|
||||
/** Typed request proxy: `await rpc.request.listDir({...})`. Waits for the bridge. */
|
||||
get request() {
|
||||
const rpc = this.rpc;
|
||||
|
||||
Reference in New Issue
Block a user