fix: keep current worker visible in collapsed sidebar
This commit is contained in:
@@ -11,7 +11,12 @@
|
|||||||
workspaceWorkersStore,
|
workspaceWorkersStore,
|
||||||
type SidebarWorker,
|
type SidebarWorker,
|
||||||
} from './worker-subscription';
|
} from './worker-subscription';
|
||||||
import { canShowWorkerInSidebar, sidebarWorkerActivity } from './workers';
|
import {
|
||||||
|
canShowWorkerInSidebar,
|
||||||
|
sidebarWorkerActivity,
|
||||||
|
visibleWorkersForSidebar,
|
||||||
|
workerOwnsSidebarPath,
|
||||||
|
} from './workers';
|
||||||
import { sidebarWorkdirMeta } from './worker-workdir-meta';
|
import { sidebarWorkdirMeta } from './worker-workdir-meta';
|
||||||
|
|
||||||
const COLLAPSED_WORKER_COUNT = 6;
|
const COLLAPSED_WORKER_COUNT = 6;
|
||||||
@@ -32,7 +37,12 @@
|
|||||||
let menuTrigger = $state<HTMLButtonElement | null>(null);
|
let menuTrigger = $state<HTMLButtonElement | null>(null);
|
||||||
let busyAction = $state<{ workerKey: string; kind: WorkerActionKind } | null>(null);
|
let busyAction = $state<{ workerKey: string; kind: WorkerActionKind } | null>(null);
|
||||||
let visibleWorkers = $derived(
|
let visibleWorkers = $derived(
|
||||||
expanded ? workers : workers.slice(0, COLLAPSED_WORKER_COUNT),
|
visibleWorkersForSidebar(workers, {
|
||||||
|
workspaceId,
|
||||||
|
currentPath,
|
||||||
|
expanded,
|
||||||
|
limit: COLLAPSED_WORKER_COUNT,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
let hiddenWorkerCount = $derived(
|
let hiddenWorkerCount = $derived(
|
||||||
Math.max(0, workers.length - COLLAPSED_WORKER_COUNT),
|
Math.max(0, workers.length - COLLAPSED_WORKER_COUNT),
|
||||||
@@ -173,13 +183,14 @@
|
|||||||
{@const activity = sidebarWorkerActivity(worker)}
|
{@const activity = sidebarWorkerActivity(worker)}
|
||||||
{@const key = workerKey(worker)}
|
{@const key = workerKey(worker)}
|
||||||
{@const label = worker.display_name || worker.label}
|
{@const label = worker.display_name || worker.label}
|
||||||
|
{@const active = workerOwnsSidebarPath(workspaceId, worker, currentPath)}
|
||||||
{@const workdir = sidebarWorkdirMeta(worker.workdir_attachments)}
|
{@const workdir = sidebarWorkdirMeta(worker.workdir_attachments)}
|
||||||
<li class="worker-nav-item" data-worker-actions={key}>
|
<li class="worker-nav-item" data-worker-actions={key}>
|
||||||
<a
|
<a
|
||||||
href={href}
|
href={href}
|
||||||
class="worker-nav-link"
|
class="worker-nav-link"
|
||||||
class:active={currentPath === href}
|
class:active
|
||||||
aria-current={currentPath === href ? 'page' : undefined}
|
aria-current={active ? 'page' : undefined}
|
||||||
>
|
>
|
||||||
<span class="worker-status-indicator">
|
<span class="worker-status-indicator">
|
||||||
{#if activity === 'worker-running'}
|
{#if activity === 'worker-running'}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import {
|
|||||||
canShowWorkerInSidebar,
|
canShowWorkerInSidebar,
|
||||||
compareWorkersForSidebar,
|
compareWorkersForSidebar,
|
||||||
sidebarWorkerActivity,
|
sidebarWorkerActivity,
|
||||||
|
visibleWorkersForSidebar,
|
||||||
|
workerOwnsSidebarPath,
|
||||||
} from "./workers.ts";
|
} from "./workers.ts";
|
||||||
import type { Worker } from "./types.ts";
|
import type { Worker } from "./types.ts";
|
||||||
|
|
||||||
@@ -79,6 +81,39 @@ Deno.test("sidebar workers sort running then idle then stopped", () => {
|
|||||||
assertEquals(workers.map((candidate) => candidate.worker_id).join(","), "2,1,4,3");
|
assertEquals(workers.map((candidate) => candidate.worker_id).join(","), "2,1,4,3");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("collapsed sidebar keeps the current Worker visible", () => {
|
||||||
|
const workers = Array.from({ length: 8 }, (_, index) =>
|
||||||
|
worker({
|
||||||
|
worker_id: String(index + 1),
|
||||||
|
resource_key: `W-${index + 1}`,
|
||||||
|
display_name: `Worker ${index + 1}`,
|
||||||
|
}));
|
||||||
|
const currentPath = "/w/workspace-1/workers/W-8-worker-8/console";
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
workerOwnsSidebarPath("workspace-1", workers[7], currentPath),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
visibleWorkersForSidebar(workers, {
|
||||||
|
workspaceId: "workspace-1",
|
||||||
|
currentPath,
|
||||||
|
expanded: false,
|
||||||
|
limit: 6,
|
||||||
|
}).map((candidate) => candidate.resource_key).join(","),
|
||||||
|
"W-1,W-2,W-3,W-4,W-5,W-8",
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
visibleWorkersForSidebar(workers, {
|
||||||
|
workspaceId: "workspace-1",
|
||||||
|
currentPath,
|
||||||
|
expanded: true,
|
||||||
|
limit: 6,
|
||||||
|
}).length,
|
||||||
|
8,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
Deno.test("fatal child stop clears the sidebar SubWorker spinner activity", () => {
|
Deno.test("fatal child stop clears the sidebar SubWorker spinner activity", () => {
|
||||||
const parent = { state: "idle", has_running_internal_workers: true };
|
const parent = { state: "idle", has_running_internal_workers: true };
|
||||||
assertEquals(sidebarWorkerActivity(parent), "subworker-running");
|
assertEquals(sidebarWorkerActivity(parent), "subworker-running");
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { workerHref } from '../resource-links';
|
||||||
|
import { ownsRoutePath } from './route-ownership';
|
||||||
import type { Worker } from './types';
|
import type { Worker } from './types';
|
||||||
|
|
||||||
export type SidebarWorkerActivity =
|
export type SidebarWorkerActivity =
|
||||||
@@ -31,6 +33,38 @@ export function canOpenWorkerConsole(
|
|||||||
return canShowWorkerInSidebar(worker);
|
return canShowWorkerInSidebar(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SidebarWorkerLink = Pick<Worker, 'resource_key' | 'display_name'>;
|
||||||
|
|
||||||
|
export function workerOwnsSidebarPath(
|
||||||
|
workspaceId: string,
|
||||||
|
worker: SidebarWorkerLink,
|
||||||
|
currentPath: string,
|
||||||
|
): boolean {
|
||||||
|
return ownsRoutePath(workerHref(workspaceId, worker), currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function visibleWorkersForSidebar<T extends SidebarWorkerLink>(
|
||||||
|
workers: readonly T[],
|
||||||
|
options: {
|
||||||
|
workspaceId: string;
|
||||||
|
currentPath: string;
|
||||||
|
expanded: boolean;
|
||||||
|
limit: number;
|
||||||
|
},
|
||||||
|
): readonly T[] {
|
||||||
|
if (options.expanded || workers.length <= options.limit) return workers;
|
||||||
|
if (options.limit <= 0) return [];
|
||||||
|
|
||||||
|
const visible = workers.slice(0, options.limit);
|
||||||
|
const currentIndex = workers.findIndex((worker) =>
|
||||||
|
workerOwnsSidebarPath(options.workspaceId, worker, options.currentPath)
|
||||||
|
);
|
||||||
|
if (currentIndex < options.limit) return visible;
|
||||||
|
|
||||||
|
visible[visible.length - 1] = workers[currentIndex];
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
type SortableWorker = Pick<
|
type SortableWorker = Pick<
|
||||||
Worker,
|
Worker,
|
||||||
'state' | 'display_name' | 'runtime_id' | 'worker_id'
|
'state' | 'display_name' | 'runtime_id' | 'worker_id'
|
||||||
|
|||||||
Reference in New Issue
Block a user