diff --git a/specs/components/orchestrator/process-tracks/types.d.ts b/specs/components/orchestrator/process-tracks/types.d.ts index 29f583aa..1840fce7 100644 --- a/specs/components/orchestrator/process-tracks/types.d.ts +++ b/specs/components/orchestrator/process-tracks/types.d.ts @@ -9,7 +9,7 @@ export type Actions = { process: (args: { tracks: Track[]; disabledUris: string[]; - /** When provided, only tracks whose uri starts with one of these prefixes are processed. */ + /** When provided, only tracks whose uri starts with one of these prefixes are processed. Explicitly listed sources are processed even if disabled. */ onlyUris?: string[]; }) => Promise; progress: () => Progress; diff --git a/src/components/orchestrator/process-tracks/worker.js b/src/components/orchestrator/process-tracks/worker.js index d8366938..130dd0d7 100644 --- a/src/components/orchestrator/process-tracks/worker.js +++ b/src/components/orchestrator/process-tracks/worker.js @@ -55,9 +55,13 @@ const process = ( !onlyUris || onlyUris.length === 0 || onlyUris.some((/** @type {string} */ uri) => t.uri.startsWith(uri)); - // Split disabled tracks out — they are preserved as-is and skipped for listing/metadata + // Split disabled tracks out — they are preserved as-is and skipped for + // listing/metadata. Sources explicitly requested via `onlyUris` are + // processed regardless: an explicit per-source run overrides the + // source's disabled state. const isDisabled = (/** @type {Track} */ t) => - disabledUris.some((/** @type {string} */ uri) => t.uri.startsWith(uri)); + disabledUris.some((/** @type {string} */ uri) => t.uri.startsWith(uri)) && + !(onlyUris ?? []).some((/** @type {string} */ uri) => t.uri.startsWith(uri)); const scopedTracks = cachedTracks.filter(inScope); const otherTracks = cachedTracks.filter((t) => !inScope(t)); diff --git a/src/facets/data/sources/facet.js b/src/facets/data/sources/facet.js index 1b02ba47..2b6d3b7e 100644 --- a/src/facets/data/sources/facet.js +++ b/src/facets/data/sources/facet.js @@ -78,6 +78,11 @@ processBtn.addEventListener("click", async () => { await processOrchestrator.process(); }); +// Source URIs explicitly queued via their own "Process source" button. +// Global runs skip disabled sources, so a disabled source only runs — and +// only shows its processing animation — when explicitly targeted. +const explicitProcessUris = signal(/** @type {Set} */ (new Set())); + //////////////////////////////////////////// // UI //////////////////////////////////////////// @@ -148,6 +153,23 @@ effect(() => { return "Status unknown"; }; + /** + * Whether this source's processing animation should run. Scoped runs only + * touch their target source, so while one is in flight only that source + * animates; otherwise (global run) enabled sources animate and disabled + * ones don't. + * + * @param {string} uri + * @param {boolean} isDisabled + */ + const isSourceProcessing = (uri, isDisabled) => { + if (!isProcessing) return false; + const explicit = explicitProcessUris.get(); + return explicit.size > 0 + ? explicit.has(trackPrefix(uri)) + : !isDisabled; + }; + const entries = Object.entries(sourcesRecord).filter( ([, sources]) => sources.length > 0, ); @@ -161,6 +183,7 @@ effect(() => { if (scheme === SCHEME_EPHEMERAL_CACHE) { const uri = `${SCHEME_EPHEMERAL_CACHE}://`; const isDisabled = sourcesOrchestrator.isDisabled(uri); + const isProcessingSource = isSourceProcessing(uri, isDisabled); const trackCount = tracks.filter((t) => t.uri.startsWith(uri)).length; return html`
  • ${SCHEME_NAMES[scheme] ?? scheme}
  • @@ -180,7 +203,7 @@ effect(() => { ?disabled="${isProcessing}" @click="${() => processSource(uri)}" > - @@ -208,6 +231,7 @@ effect(() => {
  • ${SCHEME_NAMES[scheme] ?? scheme}
  • ${sources.map(({ label, uri }) => { const isDisabled = sourcesOrchestrator.isDisabled(uri); + const isProcessingSource = isSourceProcessing(uri, isDisabled); const trackCount = tracks.filter((t) => t.uri.startsWith(trackPrefix(uri)) ).length; @@ -228,7 +252,7 @@ effect(() => { ?disabled="${isProcessing}" @click="${() => processSource(uri)}" > - @@ -268,13 +292,22 @@ async function removeEphemeralSources() { /** * Processes a single source into tracks, leaving every other source untouched. + * Explicitly processing a source overrides its disabled state. * * @param {string} uri */ async function processSource(uri) { - const output = await foundation.orchestrator.output(); - await Output.data(output.tracks); - await processOrchestrator.process([trackPrefix(uri)]); + const key = trackPrefix(uri); + explicitProcessUris.value = new Set([...explicitProcessUris.value, key]); + + try { + const output = await foundation.orchestrator.output(); + await Output.data(output.tracks); + await processOrchestrator.process([key]); + } finally { + explicitProcessUris.value = + new Set([...explicitProcessUris.value].filter((u) => u !== key)); + } } /**