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:
@@ -17,9 +17,18 @@ export class PanelState {
|
||||
readonly fsKey = signal('local');
|
||||
readonly title = signal('Local');
|
||||
readonly connecting = signal(false);
|
||||
/** Name shown in the "Connecting to X…" placeholder while connecting. */
|
||||
readonly connectingTo = signal<string | null>(null);
|
||||
readonly cwd = signal('');
|
||||
readonly entries = signal<FileEntry[]>([]);
|
||||
readonly loading = signal(false);
|
||||
/** True once an in-flight load has taken >300ms — drives the dim overlay. */
|
||||
readonly slowLoad = signal(false);
|
||||
/** Bumping this discards the result of any in-flight connect (cancel/supersede). */
|
||||
connectToken = 0;
|
||||
|
||||
/** Input on this panel is ignored while it's connecting or loading. */
|
||||
readonly busy = computed(() => this.connecting() || this.loading());
|
||||
readonly sortKey = signal<SortKey>('name');
|
||||
readonly sortAsc = signal(true);
|
||||
readonly cursor = signal(0);
|
||||
@@ -65,6 +74,7 @@ export class PanelState {
|
||||
|
||||
async load(path: string | null): Promise<void> {
|
||||
this.loading.set(true);
|
||||
const slowTimer = setTimeout(() => this.slowLoad.set(true), 300);
|
||||
try {
|
||||
const res = await this.deps.rpc.request.listDir({ sessionId: this.sessionId(), path });
|
||||
if (!res.ok) {
|
||||
@@ -77,6 +87,8 @@ export class PanelState {
|
||||
this.selected.set(new Set());
|
||||
this.filter.set('');
|
||||
} finally {
|
||||
clearTimeout(slowTimer);
|
||||
this.slowLoad.set(false);
|
||||
this.loading.set(false);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +98,7 @@ export class PanelState {
|
||||
const keepCursorName = this.sorted()[this.cursor()]?.name;
|
||||
const keepSelected = this.selected();
|
||||
this.loading.set(true);
|
||||
const slowTimer = setTimeout(() => this.slowLoad.set(true), 300);
|
||||
try {
|
||||
const res = await this.deps.rpc.request.listDir({
|
||||
sessionId: this.sessionId(),
|
||||
@@ -102,6 +115,8 @@ export class PanelState {
|
||||
const idx = this.sorted().findIndex((e) => e.name === keepCursorName);
|
||||
this.cursor.set(idx >= 0 ? idx : Math.min(this.cursor(), Math.max(0, this.sorted().length - 1)));
|
||||
} finally {
|
||||
clearTimeout(slowTimer);
|
||||
this.slowLoad.set(false);
|
||||
this.loading.set(false);
|
||||
}
|
||||
}
|
||||
@@ -349,9 +364,16 @@ export class PanelsService {
|
||||
/** Connect a panel to a server; replaces (and disconnects) its previous remote session. */
|
||||
async connectPanel(side: Side, config: ConnectionConfig, title: string): Promise<boolean> {
|
||||
const panel = this.get(side);
|
||||
const token = ++panel.connectToken;
|
||||
panel.connecting.set(true);
|
||||
panel.connectingTo.set(title);
|
||||
try {
|
||||
const res = await this.rpc.request.connect({ config });
|
||||
if (panel.connectToken !== token) {
|
||||
// cancelled or superseded while waiting — clean up the orphan session
|
||||
if (res.ok) void this.rpc.request.disconnect({ sessionId: res.sessionId });
|
||||
return false;
|
||||
}
|
||||
if (!res.ok) {
|
||||
this.toast.error(`${title}: ${res.error}`);
|
||||
return false;
|
||||
@@ -366,10 +388,22 @@ export class PanelsService {
|
||||
await panel.load(config.initialPath ?? res.homeDir);
|
||||
return true;
|
||||
} finally {
|
||||
panel.connecting.set(false);
|
||||
if (panel.connectToken === token) {
|
||||
panel.connecting.set(false);
|
||||
panel.connectingTo.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Abort waiting on an in-flight connect; the panel keeps its previous state. */
|
||||
cancelConnect(side: Side): void {
|
||||
const panel = this.get(side);
|
||||
if (!panel.connecting()) return;
|
||||
panel.connectToken++;
|
||||
panel.connecting.set(false);
|
||||
panel.connectingTo.set(null);
|
||||
}
|
||||
|
||||
async disconnectPanel(side: Side): Promise<void> {
|
||||
const panel = this.get(side);
|
||||
const sessionId = panel.sessionId();
|
||||
|
||||
Reference in New Issue
Block a user