P2: file operations with dialogs (mkdir, new file, rename, delete, chmod)
- prompt/confirm/chmod dialogs (signals-driven via DialogsService) - FileActionsService: acts on selection or cursor row of the active panel - function bar + F2/F7/F8/Delete keys; delete confirms and recurses - verified provider ops + walkTree + removeRecursive on a scratch tree Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import type { FileEntry } from '@shared/types';
|
||||
import { DialogsService } from './dialogs.service';
|
||||
import { joinPath } from './format';
|
||||
import { PanelsService, type PanelState } from './panels.service';
|
||||
import { RpcService } from './rpc.service';
|
||||
import { ToastService } from './toast.service';
|
||||
|
||||
/**
|
||||
* File operations invoked from the function bar / keyboard. They always act on
|
||||
* the ACTIVE panel: on the selection if there is one, else on the cursor row.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FileActionsService {
|
||||
private readonly panels = inject(PanelsService);
|
||||
private readonly rpc = inject(RpcService);
|
||||
private readonly toast = inject(ToastService);
|
||||
private readonly dialogs = inject(DialogsService);
|
||||
|
||||
/** Selection if non-empty, else the cursor entry. */
|
||||
targets(panel: PanelState): FileEntry[] {
|
||||
const selected = panel.selectedEntries();
|
||||
if (selected.length) return selected;
|
||||
const atCursor = panel.sorted()[panel.cursor()];
|
||||
return atCursor ? [atCursor] : [];
|
||||
}
|
||||
|
||||
mkdir(): void {
|
||||
const panel = this.panels.active();
|
||||
this.dialogs.prompt.set({
|
||||
title: 'New folder',
|
||||
placeholder: 'folder name',
|
||||
confirmLabel: 'Create',
|
||||
onConfirm: async (name) => {
|
||||
const res = await this.rpc.request.mkdir({
|
||||
sessionId: panel.sessionId(),
|
||||
path: joinPath(panel.cwd(), name),
|
||||
});
|
||||
if (!res.ok) this.toast.error(res.error);
|
||||
await panel.refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
createFile(): void {
|
||||
const panel = this.panels.active();
|
||||
this.dialogs.prompt.set({
|
||||
title: 'New file',
|
||||
placeholder: 'file name',
|
||||
confirmLabel: 'Create',
|
||||
onConfirm: async (name) => {
|
||||
const res = await this.rpc.request.createFile({
|
||||
sessionId: panel.sessionId(),
|
||||
path: joinPath(panel.cwd(), name),
|
||||
});
|
||||
if (!res.ok) this.toast.error(res.error);
|
||||
await panel.refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
rename(): void {
|
||||
const panel = this.panels.active();
|
||||
const entry = panel.sorted()[panel.cursor()];
|
||||
if (!entry) return;
|
||||
this.dialogs.prompt.set({
|
||||
title: `Rename "${entry.name}"`,
|
||||
initial: entry.name,
|
||||
confirmLabel: 'Rename',
|
||||
onConfirm: async (name) => {
|
||||
if (name === entry.name) return;
|
||||
const res = await this.rpc.request.rename({
|
||||
sessionId: panel.sessionId(),
|
||||
oldPath: entry.path,
|
||||
newPath: joinPath(panel.cwd(), name),
|
||||
});
|
||||
if (!res.ok) this.toast.error(res.error);
|
||||
await panel.refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
delete(): void {
|
||||
const panel = this.panels.active();
|
||||
const targets = this.targets(panel);
|
||||
if (!targets.length) return;
|
||||
const what =
|
||||
targets.length === 1 ? `"${targets[0].name}"` : `${targets.length} items`;
|
||||
this.dialogs.confirm.set({
|
||||
title: 'Delete',
|
||||
body: `Delete ${what}? Folders are deleted recursively. This cannot be undone.`,
|
||||
confirmLabel: 'Delete',
|
||||
destructive: true,
|
||||
onConfirm: async () => {
|
||||
const res = await this.rpc.request.deleteEntries({
|
||||
sessionId: panel.sessionId(),
|
||||
entries: targets.map(({ path, type }) => ({ path, type })),
|
||||
});
|
||||
if (!res.ok) this.toast.error(res.error);
|
||||
await panel.refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
chmod(): void {
|
||||
const panel = this.panels.active();
|
||||
const targets = this.targets(panel);
|
||||
if (!targets.length) return;
|
||||
this.dialogs.chmod.set({
|
||||
targets: targets.map((t) => t.name),
|
||||
initialMode: targets[0].mode,
|
||||
onConfirm: async (mode) => {
|
||||
const res = await this.rpc.request.chmod({
|
||||
sessionId: panel.sessionId(),
|
||||
paths: targets.map((t) => t.path),
|
||||
mode,
|
||||
});
|
||||
if (!res.ok) this.toast.error(res.error);
|
||||
await panel.refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user