Panel busy states: Connecting placeholder with Cancel, slow-load overlay

Connecting blanks the list to a fading spinner with the target name and
a Cancel button (token discards late results and orphan sessions).
Listings >300ms dim the list under a spinner. All panel input is
blocked while busy; the source picker stays usable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 16:36:44 +02:00
co-authored by Claude Fable 5
parent 4a793ca50e
commit dda596c50c
4 changed files with 90 additions and 5 deletions
+14
View File
@@ -37,6 +37,8 @@ export class FilePanel {
readonly panel = computed(() => this.panels.get(this.side()));
readonly isActive = computed(() => this.panels.activeSide() === this.side());
readonly isRemote = computed(() => this.panel().sessionId() !== 'local');
/** Input is ignored while the panel is connecting or a listing is in flight. */
readonly blocked = computed(() => this.panel().busy());
/** The copy button points toward the other panel. */
readonly copyIcon = computed(() => (this.side() === 'left' ? 'mynaArrowRight' : 'mynaArrowLeft'));
@@ -63,12 +65,18 @@ export class FilePanel {
this.panels.setActiveSide(this.side());
}
cancelConnect(): void {
this.panels.cancelConnect(this.side());
}
copyToOther(): void {
if (this.blocked()) return;
this.activate();
void this.transfers.copyToOtherPanel(this.side());
}
run(action: 'edit' | 'rename' | 'mkdir' | 'createFile' | 'chmod' | 'delete'): void {
if (this.blocked()) return;
this.activate();
void this.actions[action](this.panel());
}
@@ -148,6 +156,7 @@ export class FilePanel {
}
commitPath(): void {
if (this.blocked()) return;
const value = this.pathDraft().trim();
if (value) void this.panel().load(value);
this.listEl().nativeElement.focus();
@@ -159,6 +168,7 @@ export class FilePanel {
}
rowClick(event: MouseEvent, index: number): void {
if (this.blocked()) return;
this.activate();
const panel = this.panel();
if (event.shiftKey) {
@@ -176,6 +186,7 @@ export class FilePanel {
rowContextMenu(event: MouseEvent, index: number): void {
event.preventDefault();
event.stopPropagation();
if (this.blocked()) return;
this.activate();
const panel = this.panel();
const entry = panel.sorted()[index] ?? null;
@@ -187,16 +198,19 @@ export class FilePanel {
listContextMenu(event: MouseEvent): void {
event.preventDefault();
if (this.blocked()) return;
this.activate();
void this.actions.openContextMenu({ side: this.side(), panel: this.panel(), entry: null });
}
openAt(index: number): void {
if (this.blocked()) return;
const entry = this.panel().sorted()[index];
if (entry) this.panel().open(entry);
}
onKeydown(event: KeyboardEvent): void {
if (this.blocked()) return;
const panel = this.panel();
const key = event.key;