feat(11-02): update config-page with per-library progress display and queue-aware cancel dialog

- Add libraryId, libraryName, queuedCount to ScanProgress interface
- Replace CancelScan import with CancelCurrentScan, CancelAllScans
- Add ScanAllLibraries import and Scan All Libraries button
- Show library name in progress label (Scanning: [Library Name])
- Show queue count below progress bar when libraries queued
- Cancel dialog shows scope choice (Cancel This Library / Cancel All) when queue > 0
- Subscribe to LibraryScanQueued and LibraryScanQueueDrained events
- Track scanQueuedCount state for queue-aware UI behavior
This commit is contained in:
2026-03-09 16:09:22 -04:00
parent 9308848936
commit d01591d6cc
@@ -5,7 +5,9 @@ import { EventsOn } from '@runtime/runtime';
import { import {
Scan, Scan,
FullRescan, FullRescan,
CancelScan, CancelCurrentScan,
CancelAllScans,
ScanAllLibraries,
PauseScan, PauseScan,
ResumeScan, ResumeScan,
} from '@go/library/Library'; } from '@go/library/Library';
@@ -52,6 +54,9 @@ interface ScanProgress {
added: number; added: number;
skipped: number; skipped: number;
updated: number; updated: number;
libraryId: number;
libraryName: string;
queuedCount: number;
} }
interface ScanMetrics { interface ScanMetrics {
@@ -345,6 +350,7 @@ export class ConfigPage extends LitElement {
@state() private scanPaused = false; @state() private scanPaused = false;
@state() private showCancelDialog = false; @state() private showCancelDialog = false;
@state() private cancelMetrics: { added: number } | null = null; @state() private cancelMetrics: { added: number } | null = null;
@state() private scanQueuedCount = 0;
@state() private shortcutConflict: { @state() private shortcutConflict: {
newAction: string; newAction: string;
newKey: string; newKey: string;
@@ -357,6 +363,8 @@ export class ConfigPage extends LitElement {
private cancelScanPaused?: () => void; private cancelScanPaused?: () => void;
private cancelScanResumed?: () => void; private cancelScanResumed?: () => void;
private cancelScanCancelled?: () => void; private cancelScanCancelled?: () => void;
private cancelScanQueued?: () => void;
private cancelScanQueueDrained?: () => void;
static override styles = css` static override styles = css`
:host { :host {
@@ -901,6 +909,14 @@ export class ConfigPage extends LitElement {
Events.LibraryScanCancelled, Events.LibraryScanCancelled,
this.handleScanCancelled, this.handleScanCancelled,
); );
this.cancelScanQueued = EventsOn(
Events.LibraryScanQueued,
this.handleScanQueued,
);
this.cancelScanQueueDrained = EventsOn(
Events.LibraryScanQueueDrained,
this.handleScanQueueDrained,
);
} }
override disconnectedCallback(): void { override disconnectedCallback(): void {
@@ -911,6 +927,8 @@ export class ConfigPage extends LitElement {
this.cancelScanPaused?.(); this.cancelScanPaused?.();
this.cancelScanResumed?.(); this.cancelScanResumed?.();
this.cancelScanCancelled?.(); this.cancelScanCancelled?.();
this.cancelScanQueued?.();
this.cancelScanQueueDrained?.();
} }
private async loadLibraryConfig(): Promise<void> { private async loadLibraryConfig(): Promise<void> {
@@ -950,15 +968,26 @@ export class ConfigPage extends LitElement {
): void => { ): void => {
if (progress) { if (progress) {
this.scanProgress = progress; this.scanProgress = progress;
this.scanQueuedCount =
progress.queuedCount ?? 0;
} }
}; };
private handleScanComplete = ( private handleScanComplete = (
metrics?: ScanMetrics, metrics?: ScanMetrics,
): void => { ): void => {
this.scanProgress = null;
// If queue still has entries, don't fully reset — next scan will fire ScanStarted
if (this.scanQueuedCount > 0) {
if (metrics) {
this.metrics = metrics;
}
return;
}
this.scanning = false; this.scanning = false;
this.scanPaused = false; this.scanPaused = false;
this.scanProgress = null;
this.statusMessage = 'Scan complete.'; this.statusMessage = 'Scan complete.';
if (metrics) { if (metrics) {
@@ -966,6 +995,16 @@ export class ConfigPage extends LitElement {
} }
}; };
private handleScanQueued = (): void => {
this.scanQueuedCount++;
};
private handleScanQueueDrained = (): void => {
this.scanQueuedCount = 0;
this.scanning = false;
this.scanPaused = false;
};
private handleScanPaused = (): void => { private handleScanPaused = (): void => {
this.scanPaused = true; this.scanPaused = true;
}; };
@@ -1010,17 +1049,24 @@ export class ConfigPage extends LitElement {
private handleCancelKeep = (): void => { private handleCancelKeep = (): void => {
this.showCancelDialog = false; this.showCancelDialog = false;
this.cancelMetrics = null; this.cancelMetrics = null;
CancelScan(); CancelCurrentScan();
}; };
private handleCancelDiscard = (): void => { private handleCancelDiscard = (): void => {
this.showCancelDialog = false; this.showCancelDialog = false;
this.cancelMetrics = null; this.cancelMetrics = null;
CancelScan(); CancelCurrentScan();
this.statusMessage = this.statusMessage =
'Scan cancelled. Partial results discarded \u2014 run Full Rescan for a clean library.'; 'Scan cancelled. Partial results discarded \u2014 run Full Rescan for a clean library.';
}; };
private handleCancelAll = (): void => {
this.showCancelDialog = false;
this.cancelMetrics = null;
CancelAllScans();
this.statusMessage = 'All scanning cancelled.';
};
private handleCancelDialogDismiss = (): void => { private handleCancelDialogDismiss = (): void => {
this.showCancelDialog = false; this.showCancelDialog = false;
this.cancelMetrics = null; this.cancelMetrics = null;
@@ -1073,6 +1119,16 @@ export class ConfigPage extends LitElement {
}); });
}; };
private handleScanAll = async (): Promise<void> => {
try {
await ScanAllLibraries();
} catch (err) {
this.statusMessage =
'Scan all libraries completed with errors.';
this.scanErrors = String(err);
}
};
private handleSoftScan = async (): Promise<void> => { private handleSoftScan = async (): Promise<void> => {
try { try {
await Scan(); await Scan();
@@ -1938,6 +1994,12 @@ export class ConfigPage extends LitElement {
> >
Full Rescan Full Rescan
</button> </button>
<button
class="btn-primary"
@click=${this.handleScanAll}
>
Scan All Libraries
</button>
`} `}
</div> </div>
@@ -1985,45 +2047,98 @@ export class ConfigPage extends LitElement {
class="cancel-dialog" class="cancel-dialog"
@click=${(e: Event) => e.stopPropagation()} @click=${(e: Event) => e.stopPropagation()}
> >
<div ${this.scanQueuedCount > 0
class="cancel-dialog-title" ? html`
> <div
Cancel Scan class="cancel-dialog-title"
</div> >
<div Cancel Scanning
class="cancel-dialog-message" </div>
> <div
${this.cancelMetrics class="cancel-dialog-message"
?.added >
? `Keep ${this.cancelMetrics.added} tracks found so far, or discard?` A scan is in
: 'Cancel the current scan?'} progress with
</div> ${this
<div .scanQueuedCount}
class="cancel-dialog-actions" ${this
> .scanQueuedCount ===
<button 1
class="btn-primary" ? 'library'
@click=${this.handleCancelKeep} : 'libraries'}
> still queued.
${this </div>
.cancelMetrics <div
?.added class="cancel-dialog-actions"
? `Keep ${this.cancelMetrics.added} tracks` >
: 'Cancel Scan'} <button
</button> class="btn-warning"
<button @click=${this.handleCancelKeep}
class="btn-danger" >
@click=${this.handleCancelDiscard} Cancel
> This
Discard Library
</button> </button>
<button <button
class="btn-ghost" class="btn-danger"
@click=${this.handleCancelDialogDismiss} @click=${this.handleCancelAll}
> >
Continue Scanning Cancel
</button> All
</div> Scanning
</button>
<button
class="btn-ghost"
@click=${this.handleCancelDialogDismiss}
>
Continue
Scanning
</button>
</div>
`
: html`
<div
class="cancel-dialog-title"
>
Cancel Scan
</div>
<div
class="cancel-dialog-message"
>
${this
.cancelMetrics
?.added
? `Keep ${this.cancelMetrics.added} tracks found so far, or discard?`
: 'Cancel the current scan?'}
</div>
<div
class="cancel-dialog-actions"
>
<button
class="btn-primary"
@click=${this.handleCancelKeep}
>
${this
.cancelMetrics
?.added
? `Keep ${this.cancelMetrics.added} tracks`
: 'Cancel Scan'}
</button>
<button
class="btn-danger"
@click=${this.handleCancelDiscard}
>
Discard
</button>
<button
class="btn-ghost"
@click=${this.handleCancelDialogDismiss}
>
Continue
Scanning
</button>
</div>
`}
</div> </div>
</div> </div>
` `
@@ -2055,11 +2170,25 @@ export class ConfigPage extends LitElement {
if (!p) return nothing; if (!p) return nothing;
const libraryPrefix = p.libraryName
? `Scanning: ${p.libraryName}`
: '';
if (p.phase === 'counting') { if (p.phase === 'counting') {
return html` return html`
<div class="progress-phase"> <div class="progress-phase">
${libraryPrefix
? html`<strong>${libraryPrefix}</strong> \u2014 `
: ''}
Counting files\u2026 Counting files\u2026
</div> </div>
${p.queuedCount > 0
? html`<div class="progress-detail">
${p.queuedCount}
${p.queuedCount === 1 ? 'library' : 'libraries'}
queued
</div>`
: nothing}
`; `;
} }
@@ -2079,7 +2208,11 @@ export class ConfigPage extends LitElement {
thumbnails: 'Generating thumbnails', thumbnails: 'Generating thumbnails',
}; };
const label = phaseLabel[p.phase] ?? 'Scanning'; const baseLabel =
phaseLabel[p.phase] ?? 'Scanning';
const label = p.libraryName
? `${baseLabel}: ${p.libraryName}`
: baseLabel;
// Build detail string: "1,247 / 2,013 files (891 new, 23 updated, 356 skipped)" // Build detail string: "1,247 / 2,013 files (891 new, 23 updated, 356 skipped)"
const parts: string[] = []; const parts: string[] = [];
@@ -2121,6 +2254,13 @@ export class ConfigPage extends LitElement {
style="width: ${percent}%" style="width: ${percent}%"
></div> ></div>
</div> </div>
${p.queuedCount > 0
? html`<div class="progress-detail">
${p.queuedCount}
${p.queuedCount === 1 ? 'library' : 'libraries'}
queued
</div>`
: nothing}
`; `;
} }