← Files Biological Sequence & Alignment ViewerARCHIVED FILE
src/sequence/workspace-track-browser-dialog.tsx
17.9 KB · Sep 30, 2026 · 23:01 UTC
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import type {
SequenceListWorkspaceTrackDirectoryResult,
SequenceResolveWorkspaceTrackBundleResult,
SequenceWorkspaceTrackCandidate,
} from "../workspace-track-protocol";
import type { SequenceWorkspaceTrackBrowserClient } from "../views/workspace-tracks";
export function WorkspaceTrackBrowserButton({
browser,
}: {
browser?: SequenceWorkspaceTrackBrowserClient;
}): React.ReactElement | null {
const [open, setOpen] = useState(false);
if (browser == null) return null;
return (
<>
<button
className={buttonClass}
onClick={() => setOpen(true)}
type="button"
>
Add from workspace
</button>
{open ? (
<WorkspaceTrackBrowserDialog
browser={browser}
onClose={() => setOpen(false)}
/>
) : null}
</>
);
}
function WorkspaceTrackBrowserDialog({
browser,
onClose,
}: {
browser: SequenceWorkspaceTrackBrowserClient;
onClose: () => void;
}): React.ReactElement {
const closeButtonRef = useRef<HTMLButtonElement>(null);
const dialogRef = useRef<HTMLDivElement>(null);
const abortRef = useRef<AbortController | undefined>(undefined);
const [listing, setListing] =
useState<SequenceListWorkspaceTrackDirectoryResult>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
const [primary, setPrimary] = useState<SequenceWorkspaceTrackCandidate>();
const [index, setIndex] = useState<SequenceWorkspaceTrackCandidate>();
const [referenceFile, setReferenceFile] =
useState<SequenceWorkspaceTrackCandidate>();
const [resolution, setResolution] =
useState<SequenceResolveWorkspaceTrackBundleResult>();
const [reference, setReference] = useState("");
const [start, setStart] = useState("");
const [end, setEnd] = useState("");
const [confirmed, setConfirmed] = useState(false);
const [loadedMessage, setLoadedMessage] = useState<string>();
const run = useCallback(
async <T,>(operation: (signal: AbortSignal) => Promise<T>) => {
abortRef.current?.abort(
new DOMException(
"A newer workspace browser action started.",
"AbortError",
),
);
const controller = new AbortController();
abortRef.current = controller;
setLoading(true);
setError(undefined);
try {
return await operation(controller.signal);
} catch (caught) {
if (controller.signal.aborted) return undefined;
setError(
caught instanceof Error
? caught.message
: "The workspace track operation could not be completed.",
);
return undefined;
} finally {
if (abortRef.current === controller) {
abortRef.current = undefined;
setLoading(false);
}
}
},
[],
);
const listDirectory = useCallback(
async (directoryCandidateId?: string, cursor?: string) => {
const next = await run((signal) =>
browser.listDirectory({
cursor,
directoryCandidateId,
limit: 50,
signal,
}),
);
if (next == null) return;
setListing((current) =>
cursor == null || current == null
? next
: {
...next,
entries: [...current.entries, ...next.entries],
},
);
},
[browser, run],
);
const resolve = useCallback(
async (
selectedPrimary: SequenceWorkspaceTrackCandidate,
selectedIndex?: SequenceWorkspaceTrackCandidate,
selectedReference?: SequenceWorkspaceTrackCandidate,
) => {
setConfirmed(false);
setLoadedMessage(undefined);
setResolution(undefined);
const next = await run((signal) =>
browser.resolveBundle({
indexCandidateId: selectedIndex?.candidateId,
primaryCandidateId: selectedPrimary.candidateId,
referenceCandidateId: selectedReference?.candidateId,
signal,
}),
);
if (next == null) return;
setResolution(next);
for (const requirement of next.requirements) {
if (requirement.role === "index") {
setIndex(requirement.selectedCandidate);
} else {
setReferenceFile(requirement.selectedCandidate);
}
}
},
[browser, run],
);
useEffect(() => {
setListing(undefined);
setPrimary(undefined);
setIndex(undefined);
setReferenceFile(undefined);
setResolution(undefined);
setConfirmed(false);
void listDirectory();
const previouslyFocused = document.activeElement as HTMLElement | null;
closeButtonRef.current?.focus();
return () => {
abortRef.current?.abort(
new DOMException("Workspace browser closed.", "AbortError"),
);
previouslyFocused?.focus();
};
}, [listDirectory]);
useEffect(() => {
const dialog = dialogRef.current;
if (dialog == null) return;
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
event.preventDefault();
onClose();
return;
}
if (event.key !== "Tab") return;
const focusable = Array.from(
dialog.querySelectorAll<HTMLElement>(
"button:not([disabled]), input:not([disabled]), [href], select:not([disabled])",
),
);
if (focusable.length === 0) return;
const first = focusable[0]!;
const last = focusable.at(-1)!;
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
};
dialog.addEventListener("keydown", handleKeyDown);
return () => dialog.removeEventListener("keydown", handleKeyDown);
}, [onClose]);
const indexed = primary?.format === "bam" || primary?.format === "cram";
const startNumber = Number(start);
const endNumber = Number(end);
const validWindow =
!indexed ||
(reference.trim().length > 0 &&
Number.isInteger(startNumber) &&
startNumber > 0 &&
Number.isInteger(endNumber) &&
endNumber >= startNumber &&
endNumber - startNumber + 1 <= 100_000);
const canLoad =
confirmed &&
resolution?.ready === true &&
resolution.bundleId != null &&
validWindow &&
!loading;
async function choose(
candidate: SequenceWorkspaceTrackCandidate,
): Promise<void> {
if (candidate.role === "index") {
setIndex(candidate);
if (primary != null) await resolve(primary, candidate, referenceFile);
return;
}
if (candidate.role === "reference") {
setReferenceFile(candidate);
if (primary != null) await resolve(primary, index, candidate);
return;
}
setPrimary(candidate);
setIndex(undefined);
setReferenceFile(undefined);
await resolve(candidate);
}
async function load(): Promise<void> {
if (!canLoad || resolution?.bundleId == null || primary == null) return;
const result = await run((signal) =>
browser.loadBundle({
bundleId: resolution.bundleId!,
end: indexed ? endNumber : undefined,
reference: indexed ? reference.trim() : undefined,
signal,
start: indexed ? startNumber : undefined,
}),
);
if (result == null) return;
setLoadedMessage(
`Loaded ${
result.name
} with ${result.itemCount.toLocaleString()} items; mapping ${
result.mappingStatus
}.`,
);
setConfirmed(false);
}
return createPortal(
<div
aria-label="Workspace track browser backdrop"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
onMouseDown={(event) => {
if (event.currentTarget === event.target) onClose();
}}
>
<div
aria-labelledby="workspace-track-browser-title"
aria-modal="true"
className="flex max-h-[min(760px,90vh)] w-full max-w-3xl flex-col overflow-hidden rounded-xl border border-token-border bg-token-main-surface-primary shadow-2xl"
ref={dialogRef}
role="dialog"
>
<div className="flex items-start justify-between gap-4 border-b border-token-border px-4 py-3">
<div>
<h2
className="text-sm font-semibold"
id="workspace-track-browser-title"
>
Add related workspace evidence
</h2>
<p className="mt-1 text-xs text-token-text-secondary">
Choose a track, review any index or reference companions, then
confirm the bounded load.
</p>
</div>
<button
className={buttonClass}
onClick={onClose}
ref={closeButtonRef}
type="button"
>
Close
</button>
</div>
<div className="grid min-h-0 flex-1 gap-4 overflow-auto p-4 md:grid-cols-[1.2fr_1fr]">
<section aria-label="Workspace files" className="min-w-0 space-y-3">
<nav
aria-label="Workspace breadcrumb"
className="flex flex-wrap gap-1"
>
{listing?.breadcrumbs.map((breadcrumb) => (
<button
className={buttonClass}
disabled={loading}
key={breadcrumb.candidateId}
onClick={() => void listDirectory(breadcrumb.candidateId)}
type="button"
>
{breadcrumb.label}
</button>
))}
</nav>
<div className="max-h-96 overflow-auto rounded-lg border border-token-border">
{listing?.entries.map((entry) =>
entry.kind === "directory" ? (
<button
className="flex w-full items-center gap-2 border-b border-token-border px-3 py-2 text-left text-xs hover:bg-token-main-surface-secondary"
disabled={loading}
key={entry.candidateId}
onClick={() => void listDirectory(entry.candidateId)}
type="button"
>
<span aria-hidden="true">▸</span>
<span className="truncate">{entry.label}</span>
</button>
) : (
<button
aria-pressed={
entry.candidateId === primary?.candidateId ||
entry.candidateId === index?.candidateId ||
entry.candidateId === referenceFile?.candidateId
}
className="flex w-full items-start justify-between gap-3 border-b border-token-border px-3 py-2 text-left text-xs hover:bg-token-main-surface-secondary aria-pressed:bg-emerald-500/10"
disabled={loading}
key={entry.candidateId}
onClick={() => void choose(entry)}
type="button"
>
<span className="min-w-0">
<span className="block truncate font-medium">
{entry.label}
</span>
<span className="text-[10px] text-token-text-secondary">
{entry.workspacePath} · {entry.role}
</span>
</span>
<span className="shrink-0 text-[10px] text-token-text-secondary">
{entry.format.toUpperCase()} · {formatBytes(entry.size)}
</span>
</button>
),
)}
{listing != null && listing.entries.length === 0 ? (
<p className="p-3 text-xs text-token-text-secondary">
No supported track files or child directories are visible
here.
</p>
) : null}
</div>
{listing?.nextCursor == null ? null : (
<button
className={buttonClass}
disabled={loading}
onClick={() =>
void listDirectory(
listing.directory.candidateId,
listing.nextCursor,
)
}
type="button"
>
Load more
</button>
)}
{listing?.omittedEntries ? (
<p className="text-[10px] text-token-text-secondary">
{listing.omittedEntries.toLocaleString()} unsupported or unsafe
entries omitted.
</p>
) : null}
</section>
<section aria-label="Track bundle review" className="space-y-3">
<h3 className="text-xs font-semibold">Bundle review</h3>
{primary == null ? (
<p className="text-xs text-token-text-secondary">
Select a GFF/GTF/BED, VCF, SAM, BAM, or CRAM file.
</p>
) : (
<div className="space-y-2 rounded-lg border border-token-border p-3 text-xs">
<div>
<strong>Track:</strong> {primary.workspacePath}
</div>
{resolution?.requirements.map((requirement) => (
<div
className="space-y-1 border-t border-token-border pt-2"
key={requirement.role}
>
<div className="font-medium capitalize">
{requirement.role}: {requirement.status}
</div>
<p className="text-[10px] text-token-text-secondary">
{requirement.message}
</p>
{requirement.candidates.map((candidate) => (
<button
className={buttonClass}
key={candidate.candidateId}
onClick={() => void choose(candidate)}
type="button"
>
Use {candidate.workspacePath}
</button>
))}
</div>
))}
{indexed ? (
<fieldset className="grid gap-2 border-t border-token-border pt-2">
<legend className="text-xs font-medium">
Bounded read window
</legend>
<label className="text-[10px] text-token-text-secondary">
Contig
<input
className={inputClass}
onChange={(event) => setReference(event.target.value)}
placeholder="chr1"
value={reference}
/>
</label>
<div className="grid grid-cols-2 gap-2">
<label className="text-[10px] text-token-text-secondary">
Start
<input
className={inputClass}
min="1"
onChange={(event) => setStart(event.target.value)}
type="number"
value={start}
/>
</label>
<label className="text-[10px] text-token-text-secondary">
End
<input
className={inputClass}
min="1"
onChange={(event) => setEnd(event.target.value)}
type="number"
value={end}
/>
</label>
</div>
{!validWindow ? (
<p className="text-[10px] text-amber-600">
Enter one contig and a 1–100,000 bp inclusive window.
</p>
) : null}
</fieldset>
) : null}
<label className="flex items-start gap-2 border-t border-token-border pt-2">
<input
checked={confirmed}
disabled={resolution?.ready !== true}
onChange={(event) => setConfirmed(event.target.checked)}
type="checkbox"
/>
<span>
I confirm this track and its selected companions should be
added to the current viewer session.
</span>
</label>
<button
className={buttonClass}
disabled={!canLoad}
onClick={() => void load()}
type="button"
>
{loading ? "Working…" : "Load confirmed track"}
</button>
</div>
)}
{error == null ? null : (
<p className="text-xs text-red-500" role="alert">
{error}
</p>
)}
{loadedMessage == null ? null : (
<p className="text-xs text-emerald-600" role="status">
{loadedMessage}
</p>
)}
</section>
</div>
</div>
</div>,
document.body,
);
}
function formatBytes(bytes: number): string {
if (bytes < 1_024) return `${bytes} B`;
if (bytes < 1_024 * 1_024) return `${(bytes / 1_024).toFixed(1)} KiB`;
return `${(bytes / (1_024 * 1_024)).toFixed(1)} MiB`;
}
const buttonClass =
"inline-flex items-center rounded-md border border-token-border bg-token-main-surface-primary px-2.5 py-1.5 text-xs font-medium text-token-text-primary hover:bg-token-main-surface-secondary disabled:cursor-not-allowed disabled:opacity-50";
const inputClass =
"mt-1 block w-full rounded-md border border-token-border bg-token-main-surface-primary px-2 py-1.5 text-xs text-token-text-primary";
SHA-256: bc2a8785b70240adb088c89ef17ea9c73a2669c5bb46ab23d5a5bab516d6e07d