fixed library scan using event listeners instead of store pattern

This commit is contained in:
2026-02-24 15:04:35 -05:00
parent 1221a403cf
commit d764fefb10
14 changed files with 612 additions and 136 deletions
@@ -4,10 +4,8 @@ import {
property,
state,
} from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import { library } from '@go/models';
import { LibraryController } from '@store/controllers/library-controller';
import { Events } from '../../events';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
import '@components/cover-grid/cover-grid.js';
@@ -26,7 +24,9 @@ export class ArtistDetails extends LitElement {
private loading = true;
private libraryCtrl = new LibraryController(this);
private cancelScanComplete?: () => void;
/** Tracks the store's cached array reference to detect refreshes. */
private lastAlbumsRef: library.Album[] | null = null;
static override styles = css`
:host {
@@ -155,15 +155,18 @@ export class ArtistDetails extends LitElement {
override connectedCallback() {
super.connectedCallback();
this.loadAlbums();
this.cancelScanComplete = EventsOn(
Events.LibraryScanComplete,
() => this.loadAlbums(),
);
}
override disconnectedCallback() {
super.disconnectedCallback();
this.cancelScanComplete?.();
override updated() {
const cached = this.libraryCtrl.cachedAlbums;
if (
cached !== null &&
cached !== this.lastAlbumsRef
) {
this.lastAlbumsRef = cached;
this.loadAlbums();
}
}
/* ================================================================
@@ -4,7 +4,6 @@ import {
state,
query,
} from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import '@lit-labs/virtualizer';
import type {
LitVirtualizer,
@@ -24,7 +23,6 @@ import {
contextMenuStyles,
} from '@utils/context-menu-controller.js';
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
import { Events } from '../../events';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
import '@awesome.me/webawesome/dist/components/popup/popup.js';
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
@@ -60,9 +58,12 @@ export class ArtistsView
private libraryCtrl = new LibraryController(this);
private searchCtrl = new SearchController(this);
private ctxMenu = new ContextMenuController(this);
private cancelScanComplete?: () => void;
private wheelListenerAttached = false;
private lastSearchTerm = '';
/** Tracks the store's cached array reference to detect refreshes. */
private lastArtistsRef: library.Artist[] | null =
null;
private scrollDebounceTimer: ReturnType<
typeof setTimeout
> | null = null;
@@ -376,15 +377,10 @@ export class ArtistsView
super.connectedCallback();
this.loadCardSize();
this.loadArtists();
this.cancelScanComplete = EventsOn(
Events.LibraryScanComplete,
() => this.loadArtists(),
);
}
override disconnectedCallback() {
super.disconnectedCallback();
this.cancelScanComplete?.();
this.detachWheelListener();
if (this.scrollDebounceTimer !== null) {
@@ -404,6 +400,19 @@ export class ArtistsView
this.lastSearchTerm = currentTerm;
this.clearSelection();
}
// Re-fetch when the store delivers fresh
// data after eager refetch on invalidation.
const cached =
this.libraryCtrl.cachedArtists;
if (
cached !== null &&
cached !== this.lastArtistsRef
) {
this.lastArtistsRef = cached;
this.loadArtists();
}
}
/* ================================================================
@@ -5,7 +5,6 @@ import {
state,
query,
} from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import '@lit-labs/virtualizer';
import type {
LitVirtualizer,
@@ -17,7 +16,6 @@ import { library } from '@go/models';
import { LibraryController } from '@store/controllers/library-controller';
import { SearchController } from '@store/controllers/search-controller';
import { queueStore } from '@store/queue-store';
import { Events } from '../../events';
import '@awesome.me/webawesome/dist/components/popup/popup.js';
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
@@ -70,17 +68,19 @@ export class CoverGrid
/**
* When set, the grid displays these albums instead of
* fetching all albums from the library store. The
* component also skips the LibraryScanComplete listener
* since the parent is responsible for reloading.
* parent is responsible for reloading when data changes.
*/
@property({ type: Array, attribute: false })
externalAlbums?: library.Album[];
libraryCtrl = new LibraryController(this);
private searchCtrl = new SearchController(this);
private cancelScanComplete?: () => void;
private lastSearchTerm = '';
/** Tracks the store's cached array reference to detect refreshes. */
private lastAlbumsRef: library.Album[] | null =
null;
// Fixed grid spacing constants.
private static readonly GRID_GAP = 8;
private static readonly GRID_PADDING = 8;
@@ -481,16 +481,6 @@ export class CoverGrid
this.restoreSortPreferences();
this.loadAlbums();
// Skip the scan listener when driven by an
// external album list — the parent manages
// reloading.
if (!this.externalAlbums) {
this.cancelScanComplete = EventsOn(
Events.LibraryScanComplete,
() => this.loadAlbums(),
);
}
document.addEventListener(
'mousedown',
this.sortDropdownCloseHandler,
@@ -507,7 +497,6 @@ export class CoverGrid
override disconnectedCallback() {
super.disconnectedCallback();
this.cancelScanComplete?.();
document.removeEventListener(
'mousedown',
@@ -725,6 +714,21 @@ export class CoverGrid
);
})();
}
// Re-fetch when the store delivers fresh
// data after eager refetch on invalidation.
if (!this.externalAlbums) {
const cached =
this.libraryCtrl.cachedAlbums;
if (
cached !== null &&
cached !== this.lastAlbumsRef
) {
this.lastAlbumsRef = cached;
this.loadAlbums();
}
}
}
/* ====================================================================
@@ -4,10 +4,8 @@ import {
property,
state,
} from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import { library } from '@go/models';
import { LibraryController } from '@store/controllers/library-controller';
import { Events } from '../../events';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
import '@components/track-list/track-list.js';
@@ -23,7 +21,9 @@ export class GenreDetails extends LitElement {
private loading = true;
private libraryCtrl = new LibraryController(this);
private cancelScanComplete?: () => void;
/** Tracks the store's cached array reference to detect refreshes. */
private lastTracksRef: library.Track[] | null = null;
static override styles = css`
:host {
@@ -151,15 +151,18 @@ export class GenreDetails extends LitElement {
override connectedCallback() {
super.connectedCallback();
this.loadTracks();
this.cancelScanComplete = EventsOn(
Events.LibraryScanComplete,
() => this.loadTracks(),
);
}
override disconnectedCallback() {
super.disconnectedCallback();
this.cancelScanComplete?.();
override updated() {
const cached = this.libraryCtrl.cachedTracks;
if (
cached !== null &&
cached !== this.lastTracksRef
) {
this.lastTracksRef = cached;
this.loadTracks();
}
}
/* ================================================================
@@ -4,7 +4,6 @@ import {
state,
query,
} from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import '@lit-labs/virtualizer';
import type {
LitVirtualizer,
@@ -15,7 +14,6 @@ import { library } from '@go/models';
import { LibraryController } from '@store/controllers/library-controller';
import { SearchController } from '@store/controllers/search-controller';
import { queueStore } from '@store/queue-store';
import { Events } from '../../events';
import {
ContextMenuController,
contextMenuStyles,
@@ -60,9 +58,12 @@ export class GenresView
private libraryCtrl = new LibraryController(this);
private searchCtrl = new SearchController(this);
private ctxMenu = new ContextMenuController(this);
private cancelScanComplete?: () => void;
private wheelListenerAttached = false;
private lastSearchTerm = '';
/** Tracks the store's cached array reference to detect refreshes. */
private lastTracksRef: library.Track[] | null =
null;
private scrollDebounceTimer: ReturnType<
typeof setTimeout
> | null = null;
@@ -376,15 +377,10 @@ export class GenresView
super.connectedCallback();
this.loadCardSize();
this.loadGenres();
this.cancelScanComplete = EventsOn(
Events.LibraryScanComplete,
() => this.loadGenres(),
);
}
override disconnectedCallback() {
super.disconnectedCallback();
this.cancelScanComplete?.();
this.detachWheelListener();
if (this.scrollDebounceTimer !== null) {
@@ -404,6 +400,19 @@ export class GenresView
this.lastSearchTerm = currentTerm;
this.clearSelection();
}
// Re-fetch when the store delivers fresh
// data after eager refetch on invalidation.
const cached =
this.libraryCtrl.cachedTracks;
if (
cached !== null &&
cached !== this.lastTracksRef
) {
this.lastTracksRef = cached;
this.loadGenres();
}
}
/* ================================================================
@@ -1,7 +1,5 @@
import { LitElement, html, css, nothing } from 'lit';
import { customElement, state, query } from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
import '@awesome.me/webawesome/dist/components/popup/popup.js';
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
@@ -16,7 +14,6 @@ import {
ImportPlaylist,
} from '@go/playlist/Service';
import { PlaylistFilePicker } from '@go/frontendutil/FrontendUtil';
import { Events } from '../../events';
import type { playlist } from '@go/models';
import { queueStore } from '@store/queue-store';
import { PlayerController } from '@store/controllers/player-controller';
@@ -75,7 +72,11 @@ export class PlaylistView
| undefined {
return this.playlistSubmenuPopup;
}
private cancelScanComplete?: () => void;
/** Tracks the store's cached array reference to detect refreshes. */
private lastPlaylistsRef:
| playlist.WithTracks[]
| null = null;
private scrollDebounceTimer: ReturnType<
typeof setTimeout
> | null = null;
@@ -750,10 +751,6 @@ export class PlaylistView
override connectedCallback() {
super.connectedCallback();
this.loadPlaylists();
this.cancelScanComplete = EventsOn(
Events.LibraryScanComplete,
() => this.loadPlaylists(),
);
document.addEventListener(
'click',
this.closePlaylistCtxMenuHandler,
@@ -774,7 +771,6 @@ export class PlaylistView
override disconnectedCallback() {
super.disconnectedCallback();
this.cancelScanComplete?.();
if (this.scrollDebounceTimer !== null) {
clearTimeout(this.scrollDebounceTimer);
@@ -830,6 +826,19 @@ export class PlaylistView
};
});
}
// Re-fetch when the store delivers fresh
// data after eager refetch on invalidation.
const cached =
this.playlistCtrl.cachedPlaylists;
if (
cached !== null &&
cached !== this.lastPlaylistsRef
) {
this.lastPlaylistsRef = cached;
this.loadPlaylists();
}
}
private get scrollContainer(): HTMLElement | null {
@@ -6,7 +6,6 @@ import {
state,
query,
} from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import { SelectionController } from '@utils/selection-controller';
import type { SelectionHost } from '@utils/selection-controller';
import {
@@ -19,7 +18,6 @@ import { SearchController } from '@store/controllers/search-controller';
import { TrackListController } from '@store/controllers/tracklist-controller';
import { queueStore } from '@store/queue-store';
import { LibraryController } from '@store/controllers/library-controller';
import { Events } from '../../events';
import {
COLUMN_DEFS,
DEFAULT_COLUMN_IDS,
@@ -61,8 +59,7 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
/**
* When set, the list displays these tracks instead of
* fetching all tracks from the library store. The
* component also skips the LibraryScanComplete listener
* since the parent is responsible for reloading.
* parent is responsible for reloading when data changes.
*/
@property({ type: Array, attribute: false })
externalTracks?: library.Track[];
@@ -73,9 +70,12 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
private trackListCtrl = new TrackListController(this);
private selection = new SelectionController(this);
private ctxMenu = new ContextMenuController(this);
private cancelScanComplete?: () => void;
private lastSearchTerm = '';
/** Tracks the store's cached array reference to detect refreshes. */
private lastTracksRef: library.Track[] | null =
null;
/**
* Resolved column definitions for the currently configured
* column IDs. Falls back to defaults for any unknown ID.
@@ -942,10 +942,6 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
this.tracks = this.externalTracks;
} else {
this.loadTracks();
this.cancelScanComplete = EventsOn(
Events.LibraryScanComplete,
() => this.loadTracks(),
);
}
document.addEventListener('mousedown', this.sortDropdownCloseHandler);
document.addEventListener('click', this.clearSelectionHandler);
@@ -968,7 +964,6 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
);
this.hasRestoredScroll = false;
super.disconnectedCallback();
this.cancelScanComplete?.();
document.removeEventListener('mousedown', this.sortDropdownCloseHandler);
document.removeEventListener('click', this.clearSelectionHandler);
document.removeEventListener('mousemove', this.onColResizeMove);
@@ -1033,6 +1028,21 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
this.lastSearchTerm = currentTerm;
this.selection.clear();
}
// Re-fetch when the store delivers fresh
// data after eager refetch on invalidation.
if (!this.externalTracks) {
const cached =
this.libraryCtrl.cachedTracks;
if (
cached !== null &&
cached !== this.lastTracksRef
) {
this.lastTracksRef = cached;
this.loadTracks();
}
}
}
private previousHostWidth = 0;
+13
View File
@@ -251,6 +251,19 @@ class LibraryStore {
this.artists = null;
this.scrollPositions = { tracks: 0, albums: 0, artists: 0, genres: 0 };
this.notify();
this.eagerRefetch();
}
/**
* Re-fetches all data after cache invalidation so that
* controller subscribers receive fresh data on the next
* requestUpdate() cycle without needing their own
* LibraryScanComplete listener.
*/
private eagerRefetch(): void {
void this.getTracks();
void this.getAlbums();
void this.getArtists();
}
// ===================================================================
+1
View File
@@ -123,6 +123,7 @@ class PlaylistStore {
this.playlists = null;
this.scrollPosition = 0;
this.notify();
void this.getPlaylists();
}
// ===================================================================