fix: keep active workers visible in sidebar
This commit is contained in:
@@ -94,6 +94,9 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async ()
|
||||
const sidebar = await Deno.readTextFile(
|
||||
new URL("../sidebar/WorkspaceSidebar.svelte", import.meta.url),
|
||||
);
|
||||
const sidebarCss = await Deno.readTextFile(
|
||||
new URL("../sidebar/sidebar.css", import.meta.url),
|
||||
);
|
||||
|
||||
assert(
|
||||
workspacePage.includes("ticketsHref") &&
|
||||
@@ -132,6 +135,17 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async ()
|
||||
!workersNav.includes('aria-disabled="true"'),
|
||||
"Workers sidebar should link to the Worker list page and show state indicators with repository/workdir metadata",
|
||||
);
|
||||
assert(
|
||||
workersNav.includes("COLLAPSED_WORKER_COUNT = 6") &&
|
||||
workersNav.includes("workers.length > COLLAPSED_WORKER_COUNT") &&
|
||||
workersNav.includes("aria-expanded={expanded}") &&
|
||||
workersNav.includes("worker-overflow-chevron") &&
|
||||
!workersNav.includes("MAX_VISIBLE_WORKERS") &&
|
||||
sidebarCss.includes(".worker-overflow-toggle") &&
|
||||
sidebarCss.includes('[aria-expanded="true"] .worker-overflow-chevron') &&
|
||||
sidebarCss.includes("transform: rotate(180deg)"),
|
||||
"Workers sidebar should collapse overflow behind a graphical chevron without dropping Workers",
|
||||
);
|
||||
assert(
|
||||
!sidebar.includes("CompanionNavSection") &&
|
||||
sidebar.includes("TicketsNavSection") &&
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
} from './worker-subscription';
|
||||
import { canShowWorkerInSidebar } from './workers';
|
||||
|
||||
const MAX_VISIBLE_WORKERS = 6;
|
||||
const COLLAPSED_WORKER_COUNT = 6;
|
||||
|
||||
type Props = {
|
||||
currentPath?: string;
|
||||
@@ -17,15 +17,21 @@
|
||||
let loading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
let workers = $state<SidebarWorker[]>([]);
|
||||
let expanded = $state(false);
|
||||
let visibleWorkers = $derived(
|
||||
expanded ? workers : workers.slice(0, COLLAPSED_WORKER_COUNT),
|
||||
);
|
||||
let hiddenWorkerCount = $derived(
|
||||
Math.max(0, workers.length - COLLAPSED_WORKER_COUNT),
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
expanded = false;
|
||||
const subscription = workspaceWorkersStore(workspaceId);
|
||||
return subscription.subscribe((state) => {
|
||||
loading = state.loading;
|
||||
error = state.error;
|
||||
workers = state.workers
|
||||
.filter(canShowWorkerInSidebar)
|
||||
.slice(0, MAX_VISIBLE_WORKERS);
|
||||
workers = state.workers.filter(canShowWorkerInSidebar);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -60,7 +66,7 @@
|
||||
{:else}
|
||||
{#if error}<p class="section-state error">{error}</p>{/if}
|
||||
<ul class="nav-list" aria-label="Workers">
|
||||
{#each workers as worker (`${worker.runtime_id}:${worker.worker_id}`)}
|
||||
{#each visibleWorkers as worker (`${worker.runtime_id}:${worker.worker_id}`)}
|
||||
{@const href = workerConsoleHref(worker, workspaceId)}
|
||||
<li>
|
||||
<a
|
||||
@@ -84,5 +90,29 @@
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if workers.length > COLLAPSED_WORKER_COUNT}
|
||||
<button
|
||||
class="worker-overflow-toggle"
|
||||
type="button"
|
||||
aria-expanded={expanded}
|
||||
aria-label={expanded
|
||||
? 'Collapse Worker list'
|
||||
: `Show ${hiddenWorkerCount} more Workers`}
|
||||
title={expanded
|
||||
? 'Collapse Worker list'
|
||||
: `Show ${hiddenWorkerCount} more Workers`}
|
||||
onclick={() => (expanded = !expanded)}
|
||||
>
|
||||
<span class="worker-overflow-line" aria-hidden="true"></span>
|
||||
<svg
|
||||
class="worker-overflow-chevron"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
<span class="worker-overflow-line" aria-hidden="true"></span>
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
@@ -277,6 +277,47 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.worker-overflow-toggle {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
margin: var(--space-1) 0 0;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
align-items: center;
|
||||
border: 0;
|
||||
border-radius: var(--radius-soft);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
.worker-overflow-toggle:hover,
|
||||
.worker-overflow-toggle:focus-visible {
|
||||
background: var(--interactive-hover);
|
||||
color: var(--text-strong);
|
||||
}
|
||||
.worker-overflow-toggle:focus-visible {
|
||||
outline: 1px solid var(--accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.worker-overflow-line {
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
opacity: 0.45;
|
||||
}
|
||||
.worker-overflow-chevron {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 1.75;
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
.worker-overflow-toggle[aria-expanded="true"] .worker-overflow-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@keyframes worker-status-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
|
||||
@@ -66,7 +66,7 @@ Deno.test("live runtime workers are sidebar targets and console targets", () =>
|
||||
assertEquals(canOpenWorkerConsole(liveWorker), true);
|
||||
});
|
||||
|
||||
Deno.test("sidebar workers sort idle then running then stopped", () => {
|
||||
Deno.test("sidebar workers sort running then idle then stopped", () => {
|
||||
const workers = [
|
||||
worker({ worker_id: "3", display_name: "Stopped", state: "stopped" }),
|
||||
worker({ worker_id: "2", display_name: "Running", state: "running" }),
|
||||
@@ -74,5 +74,5 @@ Deno.test("sidebar workers sort idle then running then stopped", () => {
|
||||
worker({ worker_id: "1", display_name: "Idle A", state: "idle" }),
|
||||
];
|
||||
workers.sort(compareWorkersForSidebar);
|
||||
assertEquals(workers.map((candidate) => candidate.worker_id).join(","), "1,4,2,3");
|
||||
assertEquals(workers.map((candidate) => candidate.worker_id).join(","), "2,1,4,3");
|
||||
});
|
||||
|
||||
@@ -15,9 +15,9 @@ type SortableWorker = Pick<
|
||||
|
||||
function workerStateRank(state: Worker['state']): number {
|
||||
switch (state) {
|
||||
case 'idle':
|
||||
return 0;
|
||||
case 'running':
|
||||
return 0;
|
||||
case 'idle':
|
||||
return 1;
|
||||
case 'stopped':
|
||||
return 2;
|
||||
|
||||
Reference in New Issue
Block a user