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:
@@ -1,10 +1,11 @@
|
||||
import { Injectable, computed, effect, inject, signal } from '@angular/core';
|
||||
import { Injectable, computed, inject, signal } from '@angular/core';
|
||||
import type { TransferSnapshot } from '@shared/types';
|
||||
import { PanelsService } from './panels.service';
|
||||
import { PanelsService, type Side } from './panels.service';
|
||||
import { RpcService } from './rpc.service';
|
||||
import { ToastService } from './toast.service';
|
||||
|
||||
const AUTO_DISMISS_COMPLETED_MS = 5000;
|
||||
const ACTIVE_STATES = ['queued', 'scanning', 'running'];
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TransfersService {
|
||||
@@ -15,19 +16,26 @@ export class TransfersService {
|
||||
private readonly jobMap = signal<ReadonlyMap<string, TransferSnapshot>>(new Map());
|
||||
readonly jobs = computed(() => [...this.jobMap().values()]);
|
||||
readonly hasJobs = computed(() => this.jobMap().size > 0);
|
||||
readonly activeJobs = computed(() => this.jobs().filter((j) => ACTIVE_STATES.includes(j.state)));
|
||||
readonly currentJob = computed(
|
||||
() => this.jobs().find((j) => j.state === 'running' || j.state === 'scanning') ?? null,
|
||||
);
|
||||
readonly queuedCount = computed(() => this.jobs().filter((j) => j.state === 'queued').length);
|
||||
readonly failedJobs = computed(() => this.jobs().filter((j) => j.state === 'failed'));
|
||||
|
||||
/** Whether the detailed per-job list is shown (toggled from the status bar). */
|
||||
readonly expanded = signal(false);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const snapshot = this.rpc.transferUpdate();
|
||||
if (snapshot) this.apply(snapshot);
|
||||
});
|
||||
if (this.rpc.available) {
|
||||
this.rpc.onTransferUpdate((snapshot) => this.apply(snapshot));
|
||||
}
|
||||
}
|
||||
|
||||
/** Copy the active panel's targets into the other panel's cwd (the F5 action). */
|
||||
async copyToOtherPanel(): Promise<void> {
|
||||
const side = this.panels.activeSide();
|
||||
const src = this.panels.get(side);
|
||||
const dst = this.panels.other(side);
|
||||
/** Copy the given panel's targets into the other panel's cwd (F5 / toolbar). */
|
||||
async copyToOtherPanel(fromSide: Side): Promise<void> {
|
||||
const src = this.panels.get(fromSide);
|
||||
const dst = this.panels.other(fromSide);
|
||||
|
||||
const selected = src.selectedEntries();
|
||||
const atCursor = src.sorted()[src.cursor()];
|
||||
@@ -76,6 +84,7 @@ export class TransfersService {
|
||||
this.refreshPanelsOn(snapshot.dstSessionId);
|
||||
setTimeout(() => void this.dismiss(snapshot.id), AUTO_DISMISS_COMPLETED_MS);
|
||||
} else if (snapshot.state === 'failed') {
|
||||
this.expanded.set(true);
|
||||
this.toast.error(`Transfer "${snapshot.name}" failed: ${snapshot.error ?? 'unknown error'}`);
|
||||
this.refreshPanelsOn(snapshot.dstSessionId);
|
||||
} else if (snapshot.state === 'cancelled') {
|
||||
@@ -84,7 +93,7 @@ export class TransfersService {
|
||||
}
|
||||
|
||||
private refreshPanelsOn(sessionId: string): void {
|
||||
for (const panel of [this.panels.left, this.panels.right]) {
|
||||
for (const panel of this.panels.allPanels()) {
|
||||
if (panel.sessionId() === sessionId) void panel.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user