178 lines
6.0 KiB
TypeScript
178 lines
6.0 KiB
TypeScript
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 { EditorDialog } from './dialogs/editor-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';
|
|
import { StatusBar } from './status-bar/status-bar';
|
|
import { TabBar } from './tab-bar/tab-bar';
|
|
import { ToastService } from './toast.service';
|
|
import { TransferBar } from './transfer-bar/transfer-bar';
|
|
import { TransfersService } from './transfers.service';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
imports: [
|
|
NgIcon,
|
|
FilePanel,
|
|
TabBar,
|
|
StatusBar,
|
|
PromptDialog,
|
|
ConfirmDialog,
|
|
ChmodDialog,
|
|
OverwriteDialog,
|
|
EditorDialog,
|
|
TransferBar,
|
|
ConnectionManager,
|
|
],
|
|
templateUrl: './app.html',
|
|
host: { '(window:keydown)': 'onWindowKeydown($event)' },
|
|
})
|
|
export class App {
|
|
readonly rpc = inject(RpcService);
|
|
readonly toast = inject(ToastService);
|
|
readonly dialogs = inject(DialogsService);
|
|
readonly panels = inject(PanelsService);
|
|
readonly actions = inject(FileActionsService);
|
|
readonly connections = inject(ConnectionsService);
|
|
private readonly transfers = inject(TransfersService);
|
|
|
|
constructor() {
|
|
document.title = BRANDING.appName;
|
|
if (this.rpc.available) {
|
|
this.rpc.onMenuAction((action) => this.handleMenuAction(action));
|
|
void this.promptForEditLeftovers();
|
|
}
|
|
}
|
|
|
|
/** Temp files from external edits in a previous run (crash / quit mid-edit). */
|
|
private async promptForEditLeftovers(): Promise<void> {
|
|
const res = await this.rpc.request.getEditLeftovers({});
|
|
if (!res.ok || !res.names.length) return;
|
|
const shown = res.names.slice(0, 5).join(', ');
|
|
const more = res.names.length > 5 ? `, +${res.names.length - 5} more` : '';
|
|
this.dialogs.confirm.set({
|
|
title: 'Leftover edited files',
|
|
body: `A previous session left ${res.names.length} temporary edited file(s) behind (${shown}${more}). Clear them?`,
|
|
confirmLabel: 'Clear',
|
|
destructive: true,
|
|
onConfirm: () => void this.rpc.request.clearEditLeftovers({}),
|
|
});
|
|
}
|
|
|
|
/** Native application-menu actions forwarded from the bun process. */
|
|
private handleMenuAction(action: string): void {
|
|
if (action.startsWith('ctx-')) {
|
|
this.handleContextAction(action);
|
|
return;
|
|
}
|
|
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 'edit-file':
|
|
void this.actions.edit(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. */
|
|
onWindowKeydown(event: KeyboardEvent): void {
|
|
const mod = event.metaKey || event.ctrlKey;
|
|
if (mod && event.key.toLowerCase() === 't') {
|
|
event.preventDefault();
|
|
this.panels.newWorkspace();
|
|
} else if (mod && event.key.toLowerCase() === 'w') {
|
|
event.preventDefault();
|
|
this.panels.closeWorkspace(this.panels.activeWorkspaceId());
|
|
} else if (event.ctrlKey && event.key === 'Tab') {
|
|
event.preventDefault();
|
|
this.panels.cycleWorkspace(event.shiftKey ? -1 : 1);
|
|
} else if (mod && !event.shiftKey && !event.altKey) {
|
|
// ⌘1–⌘8 jump to that tab, ⌘9 to the last (Ctrl+digit elsewhere), like
|
|
// Firefox. Matched on event.code so the digit row works on any layout.
|
|
const digit = /^(?:Digit|Numpad)([1-9])$/.exec(event.code)?.[1];
|
|
if (!digit) return;
|
|
event.preventDefault();
|
|
this.panels.goToWorkspace(digit === '9' ? -1 : Number(digit) - 1);
|
|
}
|
|
}
|
|
|
|
/** Context-menu actions apply to the row/panel remembered at right-click time. */
|
|
private handleContextAction(action: string): void {
|
|
const target = this.actions.menuTarget;
|
|
if (!target) return;
|
|
const { panel, side, entry } = target;
|
|
switch (action) {
|
|
case 'ctx-open':
|
|
if (entry) panel.open(entry);
|
|
break;
|
|
case 'ctx-edit':
|
|
void this.actions.edit(panel);
|
|
break;
|
|
case 'ctx-copy':
|
|
void this.transfers.copyToOtherPanel(side);
|
|
break;
|
|
case 'ctx-rename':
|
|
this.actions.rename(panel);
|
|
break;
|
|
case 'ctx-chmod':
|
|
this.actions.chmod(panel);
|
|
break;
|
|
case 'ctx-delete':
|
|
this.actions.delete(panel);
|
|
break;
|
|
case 'ctx-trash':
|
|
if (entry) void this.actions.trash(panel, entry);
|
|
break;
|
|
case 'ctx-new-folder':
|
|
this.actions.mkdir(panel);
|
|
break;
|
|
case 'ctx-new-file':
|
|
this.actions.createFile(panel);
|
|
break;
|
|
case 'ctx-refresh':
|
|
void panel.refresh();
|
|
break;
|
|
}
|
|
}
|
|
|
|
toastIcon(kind: 'error' | 'info' | 'success'): string {
|
|
if (kind === 'error') return 'mynaXCircle';
|
|
if (kind === 'success') return 'mynaCheckCircle';
|
|
return 'mynaCheck';
|
|
}
|
|
}
|