v1.1: fix transfer freeze, session tabs, per-panel toolbars, status bar
Freeze root cause: TransfersService consumed transferUpdate through an effect that both read and wrote the jobMap signal — the first progress message spun the effect into an infinite re-run loop, pegging the webview thread while the copy completed fine bun-side. Push messages now flow through plain callback registries on RpcService (also stops signal coalescing from dropping progress snapshots). Verified live: a 60MB boot-time self-test copy streamed progress into the running UI and the post-completion panel refreshes arrived. - Tabs: workspaces (left+right PanelState pair each) in PanelsService; tab bar replaces the header; sessions persist in background tabs; Cmd+T/Cmd+W/Ctrl+Tab; closing a tab disconnects its sessions; last tab can't close - Per-panel toolbar (top, icon-only): direction-explicit copy (-> / <-), rename, new folder/file, chmod, delete; panel footers removed - VSCode-style status bar: transfer summary (spinner/percent/queued/failures, click to expand the job list) + active panel info; function bar removed - transfer details list is collapsible, auto-expands on failure Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,17 +2,16 @@ 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 { 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.
|
||||
* File operations invoked from a panel's toolbar / keyboard. They act on the
|
||||
* GIVEN 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);
|
||||
@@ -25,8 +24,7 @@ export class FileActionsService {
|
||||
return atCursor ? [atCursor] : [];
|
||||
}
|
||||
|
||||
mkdir(): void {
|
||||
const panel = this.panels.active();
|
||||
mkdir(panel: PanelState): void {
|
||||
this.dialogs.prompt.set({
|
||||
title: 'New folder',
|
||||
placeholder: 'folder name',
|
||||
@@ -42,8 +40,7 @@ export class FileActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
createFile(): void {
|
||||
const panel = this.panels.active();
|
||||
createFile(panel: PanelState): void {
|
||||
this.dialogs.prompt.set({
|
||||
title: 'New file',
|
||||
placeholder: 'file name',
|
||||
@@ -59,8 +56,7 @@ export class FileActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
rename(): void {
|
||||
const panel = this.panels.active();
|
||||
rename(panel: PanelState): void {
|
||||
const entry = panel.sorted()[panel.cursor()];
|
||||
if (!entry) return;
|
||||
this.dialogs.prompt.set({
|
||||
@@ -80,8 +76,7 @@ export class FileActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
delete(): void {
|
||||
const panel = this.panels.active();
|
||||
delete(panel: PanelState): void {
|
||||
const targets = this.targets(panel);
|
||||
if (!targets.length) return;
|
||||
const what =
|
||||
@@ -102,8 +97,7 @@ export class FileActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
chmod(): void {
|
||||
const panel = this.panels.active();
|
||||
chmod(panel: PanelState): void {
|
||||
const targets = this.targets(panel);
|
||||
if (!targets.length) return;
|
||||
this.dialogs.chmod.set({
|
||||
|
||||
Reference in New Issue
Block a user