refactor(frontend): adopt the lifecycle and the notification surface
The remaining views, brought onto the two mechanisms added earlier in this series. The lifecycle: every cached primary view moves its document listeners, intervals and event subscriptions off connect/disconnect and onto `viewActivated`/`viewDeactivated`, so `autotag-view` stops fielding keystrokes from Settings, `downloads-view`'s 30 s clock stops ticking for the session, and an off-screen view stops rendering on every search keystroke. `autotag-view` keeps a document listener only for Escape, whose dialogs Phase 5 migrates to wa-dialog anyway. The voice: the silent failures now speak — scan and full rescan (with a guard against the double-click the coalescing window allowed), job pause/resume/cancel, playlist delete, download request pause/remove/ clear, add and rename library, add-to-playlist, playlist track removal, autotag's dialogs and its apply, and favourite reverts. Both private toasts are gone, along with their CSS and keyframes. Playlist delete (single and the multi-select loop), download-request removal, download-client removal and a queue clear over 20 tracks ask first. Loading, empty and failed become three states rather than one, in `track-list` and `genre-details` — the first is on the first screen a new user ever sees — and the Settings index panel seeds itself with `GetIndexStatus()` instead of waiting forever for a change event. `smart-playlist-editor` and `download-picker` take the request-version guard `explore-view` already had. `track-details` loads through one memoised dynamic import in all ten openers, which is what takes its 42 kB out of the startup chunk: an un-upgraded custom element is a real HTMLElement on which `?.show()` throws, so each opener awaits it before touching the element its template already rendered.
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import { describeError } from '@utils/describe-error';
|
||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import '@components/track-list/track-list.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
@@ -27,6 +28,12 @@ export class GenreDetails extends LitElement {
|
||||
@state()
|
||||
private loading = true;
|
||||
|
||||
/** A failed genre query used to be handed to `<track-list>` as an
|
||||
* empty array, so it was indistinguishable from a slow one
|
||||
* (errors.M2). */
|
||||
@state()
|
||||
private loadError = '';
|
||||
|
||||
private scanCompleteCleanup: (() => void) | null =
|
||||
null;
|
||||
|
||||
@@ -38,6 +45,21 @@ export class GenreDetails extends LitElement {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.load-error {
|
||||
color: var(--yj-text-secondary, #b3b3b3);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.load-error button {
|
||||
background: none;
|
||||
border: 1px solid var(--yj-border, #495057);
|
||||
border-radius: 4px;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
/* ====================================
|
||||
* Header
|
||||
* ==================================== */
|
||||
@@ -179,6 +201,8 @@ export class GenreDetails extends LitElement {
|
||||
private async loadTracks() {
|
||||
if (!this.genreName) return;
|
||||
|
||||
this.loadError = '';
|
||||
|
||||
try {
|
||||
const libId =
|
||||
libraryStore.getSelectedLibraryId();
|
||||
@@ -192,11 +216,12 @@ export class GenreDetails extends LitElement {
|
||||
this.genreName,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error loading genre tracks:',
|
||||
error,
|
||||
);
|
||||
console.error('Error loading genre tracks:', error);
|
||||
this.tracks = [];
|
||||
this.loadError = describeError(
|
||||
error,
|
||||
`The tracks for “${this.genreName}” could not be loaded.`,
|
||||
);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -274,9 +299,19 @@ export class GenreDetails extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<track-list
|
||||
.externalTracks=${this.tracks}
|
||||
></track-list>
|
||||
${this.loadError
|
||||
? html`<div class="load-error" data-testid="genre-error">
|
||||
<p>${this.loadError}</p>
|
||||
<button
|
||||
type="button"
|
||||
@click=${() => void this.loadTracks()}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>`
|
||||
: html`<track-list
|
||||
.externalTracks=${this.tracks}
|
||||
></track-list>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user