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(
|
const sidebar = await Deno.readTextFile(
|
||||||
new URL("../sidebar/WorkspaceSidebar.svelte", import.meta.url),
|
new URL("../sidebar/WorkspaceSidebar.svelte", import.meta.url),
|
||||||
);
|
);
|
||||||
|
const sidebarCss = await Deno.readTextFile(
|
||||||
|
new URL("../sidebar/sidebar.css", import.meta.url),
|
||||||
|
);
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
workspacePage.includes("ticketsHref") &&
|
workspacePage.includes("ticketsHref") &&
|
||||||
@@ -132,6 +135,17 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async ()
|
|||||||
!workersNav.includes('aria-disabled="true"'),
|
!workersNav.includes('aria-disabled="true"'),
|
||||||
"Workers sidebar should link to the Worker list page and show state indicators with repository/workdir metadata",
|
"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(
|
assert(
|
||||||
!sidebar.includes("CompanionNavSection") &&
|
!sidebar.includes("CompanionNavSection") &&
|
||||||
sidebar.includes("TicketsNavSection") &&
|
sidebar.includes("TicketsNavSection") &&
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
} from './worker-subscription';
|
} from './worker-subscription';
|
||||||
import { canShowWorkerInSidebar } from './workers';
|
import { canShowWorkerInSidebar } from './workers';
|
||||||
|
|
||||||
const MAX_VISIBLE_WORKERS = 6;
|
const COLLAPSED_WORKER_COUNT = 6;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentPath?: string;
|
currentPath?: string;
|
||||||
@@ -17,15 +17,21 @@
|
|||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
let workers = $state<SidebarWorker[]>([]);
|
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(() => {
|
$effect(() => {
|
||||||
|
expanded = false;
|
||||||
const subscription = workspaceWorkersStore(workspaceId);
|
const subscription = workspaceWorkersStore(workspaceId);
|
||||||
return subscription.subscribe((state) => {
|
return subscription.subscribe((state) => {
|
||||||
loading = state.loading;
|
loading = state.loading;
|
||||||
error = state.error;
|
error = state.error;
|
||||||
workers = state.workers
|
workers = state.workers.filter(canShowWorkerInSidebar);
|
||||||
.filter(canShowWorkerInSidebar)
|
|
||||||
.slice(0, MAX_VISIBLE_WORKERS);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -60,7 +66,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#if error}<p class="section-state error">{error}</p>{/if}
|
{#if error}<p class="section-state error">{error}</p>{/if}
|
||||||
<ul class="nav-list" aria-label="Workers">
|
<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)}
|
{@const href = workerConsoleHref(worker, workspaceId)}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@@ -84,5 +90,29 @@
|
|||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</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}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -277,6 +277,47 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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 {
|
@keyframes worker-status-spin {
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ Deno.test("live runtime workers are sidebar targets and console targets", () =>
|
|||||||
assertEquals(canOpenWorkerConsole(liveWorker), true);
|
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 = [
|
const workers = [
|
||||||
worker({ worker_id: "3", display_name: "Stopped", state: "stopped" }),
|
worker({ worker_id: "3", display_name: "Stopped", state: "stopped" }),
|
||||||
worker({ worker_id: "2", display_name: "Running", state: "running" }),
|
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" }),
|
worker({ worker_id: "1", display_name: "Idle A", state: "idle" }),
|
||||||
];
|
];
|
||||||
workers.sort(compareWorkersForSidebar);
|
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 {
|
function workerStateRank(state: Worker['state']): number {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'idle':
|
|
||||||
return 0;
|
|
||||||
case 'running':
|
case 'running':
|
||||||
|
return 0;
|
||||||
|
case 'idle':
|
||||||
return 1;
|
return 1;
|
||||||
case 'stopped':
|
case 'stopped':
|
||||||
return 2;
|
return 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user