added genre cache
This commit is contained in:
+1
-1
@@ -6,8 +6,8 @@
|
|||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
<link href="./index.css" rel="stylesheet" />
|
<link href="./index.css" rel="stylesheet" />
|
||||||
<title>yellowjacket</title>
|
<title>yellowjacket</title>
|
||||||
|
<script src="/index.ts" type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
<script src="/index.ts" type="module"></script>
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header class="top-bar">
|
<header class="top-bar">
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import type { SearchBar } from '@components/search-bar/search-bar.ts';
|
|||||||
import '@awesome.me/webawesome/dist/styles/themes/default.css';
|
import '@awesome.me/webawesome/dist/styles/themes/default.css';
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
import { setBasePath } from '@awesome.me/webawesome/dist/webawesome.js';
|
import { setBasePath } from '@awesome.me/webawesome/dist/webawesome.js';
|
||||||
import { libraryStore } from '@store/library-store';
|
|
||||||
import { playlistStore } from '@store/playlist-store';
|
|
||||||
import { queueStore } from '@store/queue-store';
|
import { queueStore } from '@store/queue-store';
|
||||||
import { searchStore } from '@store/search-store';
|
import { searchStore } from '@store/search-store';
|
||||||
// Importing the theme store triggers initialization: it fetches the saved
|
// Importing the theme store triggers initialization: it fetches the saved
|
||||||
@@ -32,12 +30,6 @@ import type { DragActiveDetail } from '@utils/drag-controller';
|
|||||||
|
|
||||||
setBasePath('/dist/webawesome');
|
setBasePath('/dist/webawesome');
|
||||||
|
|
||||||
// Pre-fetch data for views not yet mounted so they're cached when navigated to.
|
|
||||||
// These are fire-and-forget — the singleton stores deduplicate concurrent fetches,
|
|
||||||
// so if a component mounts before this completes, it joins the in-flight request.
|
|
||||||
libraryStore.getAlbums();
|
|
||||||
playlistStore.getPlaylists();
|
|
||||||
|
|
||||||
// Navigation event listener for view switching
|
// Navigation event listener for view switching
|
||||||
document.addEventListener('navigate', (e: Event) => {
|
document.addEventListener('navigate', (e: Event) => {
|
||||||
const { view } = (e as CustomEvent).detail;
|
const { view } = (e as CustomEvent).detail;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
contextMenuStyles,
|
contextMenuStyles,
|
||||||
} from '@utils/context-menu-controller.js';
|
} from '@utils/context-menu-controller.js';
|
||||||
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
||||||
|
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||||
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||||
@@ -360,6 +361,7 @@ export class ArtistsView
|
|||||||
);
|
);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,8 @@ import type {
|
|||||||
VisibilityChangedEvent,
|
VisibilityChangedEvent,
|
||||||
} from '@lit-labs/virtualizer';
|
} from '@lit-labs/virtualizer';
|
||||||
import { grid } from '@lit-labs/virtualizer/layouts/grid.js';
|
import { grid } from '@lit-labs/virtualizer/layouts/grid.js';
|
||||||
import {
|
import { GetTracksByGenre } from '@go/library/Library';
|
||||||
GetAllGenresWithCounts,
|
import type { library } from '@go/models';
|
||||||
GetTracksByGenre,
|
|
||||||
} from '@go/library/Library';
|
|
||||||
import { EventsOn } from '@runtime/runtime';
|
|
||||||
import { Events } from '../../events';
|
|
||||||
import { LibraryController } from '@store/controllers/library-controller';
|
import { LibraryController } from '@store/controllers/library-controller';
|
||||||
import { SearchController } from '@store/controllers/search-controller';
|
import { SearchController } from '@store/controllers/search-controller';
|
||||||
import { queueStore } from '@store/queue-store';
|
import { queueStore } from '@store/queue-store';
|
||||||
@@ -24,6 +20,7 @@ import {
|
|||||||
contextMenuStyles,
|
contextMenuStyles,
|
||||||
} from '@utils/context-menu-controller.js';
|
} from '@utils/context-menu-controller.js';
|
||||||
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
||||||
|
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||||
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||||
@@ -66,8 +63,11 @@ export class GenresView
|
|||||||
private ctxMenu = new ContextMenuController(this);
|
private ctxMenu = new ContextMenuController(this);
|
||||||
private wheelListenerAttached = false;
|
private wheelListenerAttached = false;
|
||||||
private lastSearchTerm = '';
|
private lastSearchTerm = '';
|
||||||
private scanCompleteCleanup: (() => void) | null =
|
|
||||||
null;
|
/** Tracks the store's cached array reference to detect refreshes. */
|
||||||
|
private lastGenresRef:
|
||||||
|
| library.GenreWithCount[]
|
||||||
|
| null = null;
|
||||||
|
|
||||||
private scrollDebounceTimer: ReturnType<
|
private scrollDebounceTimer: ReturnType<
|
||||||
typeof setTimeout
|
typeof setTimeout
|
||||||
@@ -379,22 +379,12 @@ export class GenresView
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.loadCardSize();
|
this.loadCardSize();
|
||||||
this.loadGenres();
|
this.loadGenres();
|
||||||
|
|
||||||
this.scanCompleteCleanup = EventsOn(
|
|
||||||
Events.LibraryScanComplete,
|
|
||||||
() => this.loadGenres(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override disconnectedCallback() {
|
override disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this.detachWheelListener();
|
this.detachWheelListener();
|
||||||
|
|
||||||
if (this.scanCompleteCleanup) {
|
|
||||||
this.scanCompleteCleanup();
|
|
||||||
this.scanCompleteCleanup = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scrollDebounceTimer !== null) {
|
if (this.scrollDebounceTimer !== null) {
|
||||||
clearTimeout(this.scrollDebounceTimer);
|
clearTimeout(this.scrollDebounceTimer);
|
||||||
}
|
}
|
||||||
@@ -412,6 +402,19 @@ export class GenresView
|
|||||||
this.lastSearchTerm = currentTerm;
|
this.lastSearchTerm = currentTerm;
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-fetch when the store delivers fresh
|
||||||
|
// data after eager refetch on invalidation.
|
||||||
|
const cached =
|
||||||
|
this.libraryCtrl.cachedGenres;
|
||||||
|
|
||||||
|
if (
|
||||||
|
cached !== null &&
|
||||||
|
cached !== this.lastGenresRef
|
||||||
|
) {
|
||||||
|
this.lastGenresRef = cached;
|
||||||
|
this.loadGenres();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================================================================
|
/* ================================================================
|
||||||
@@ -423,7 +426,7 @@ export class GenresView
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
const rows =
|
const rows =
|
||||||
await GetAllGenresWithCounts();
|
await this.libraryCtrl.getGenres();
|
||||||
|
|
||||||
this.genres = (rows ?? []).map((r) => ({
|
this.genres = (rows ?? []).map((r) => ({
|
||||||
name: r.Name,
|
name: r.Name,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ContextMenuController,
|
ContextMenuController,
|
||||||
contextMenuStyles,
|
contextMenuStyles,
|
||||||
} from '@utils/context-menu-controller.js';
|
} from '@utils/context-menu-controller.js';
|
||||||
|
|
||||||
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
||||||
import { PlayerController } from '@store/controllers/player-controller';
|
import { PlayerController } from '@store/controllers/player-controller';
|
||||||
import { SearchController } from '@store/controllers/search-controller';
|
import { SearchController } from '@store/controllers/search-controller';
|
||||||
@@ -728,6 +729,7 @@ export class TrackList extends LitElement implements SelectionHost, ContextMenuH
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.sort-anchor {
|
.sort-anchor {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ export class LibraryController implements ReactiveController {
|
|||||||
return libraryStore.getArtists();
|
return libraryStore.getArtists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getGenres(): Promise<library.GenreWithCount[]> {
|
||||||
|
return libraryStore.getGenres();
|
||||||
|
}
|
||||||
|
|
||||||
async getAlbumsByArtist(
|
async getAlbumsByArtist(
|
||||||
artistID: number,
|
artistID: number,
|
||||||
): Promise<library.Album[]> {
|
): Promise<library.Album[]> {
|
||||||
@@ -81,6 +85,10 @@ export class LibraryController implements ReactiveController {
|
|||||||
return libraryStore.getCachedArtists();
|
return libraryStore.getCachedArtists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get cachedGenres(): library.GenreWithCount[] | null {
|
||||||
|
return libraryStore.getCachedGenres();
|
||||||
|
}
|
||||||
|
|
||||||
get tracksLoading(): boolean {
|
get tracksLoading(): boolean {
|
||||||
return libraryStore.isTracksLoading();
|
return libraryStore.isTracksLoading();
|
||||||
}
|
}
|
||||||
@@ -93,6 +101,10 @@ export class LibraryController implements ReactiveController {
|
|||||||
return libraryStore.isArtistsLoading();
|
return libraryStore.isArtistsLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get genresLoading(): boolean {
|
||||||
|
return libraryStore.isGenresLoading();
|
||||||
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// SCROLL POSITION
|
// SCROLL POSITION
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
GetAllTracks,
|
GetAllTracks,
|
||||||
GetAllAlbums,
|
GetAllAlbums,
|
||||||
GetAllArtists,
|
GetAllArtists,
|
||||||
|
GetAllGenresWithCounts,
|
||||||
GetAlbumsByArtist,
|
GetAlbumsByArtist,
|
||||||
} from '@go/library/Library';
|
} from '@go/library/Library';
|
||||||
import type { library } from '@go/models';
|
import type { library } from '@go/models';
|
||||||
@@ -28,10 +29,12 @@ class LibraryStore {
|
|||||||
private tracks: library.Track[] | null = null;
|
private tracks: library.Track[] | null = null;
|
||||||
private albums: library.Album[] | null = null;
|
private albums: library.Album[] | null = null;
|
||||||
private artists: library.Artist[] | null = null;
|
private artists: library.Artist[] | null = null;
|
||||||
|
private genres: library.GenreWithCount[] | null = null;
|
||||||
|
|
||||||
private tracksLoading = false;
|
private tracksLoading = false;
|
||||||
private albumsLoading = false;
|
private albumsLoading = false;
|
||||||
private artistsLoading = false;
|
private artistsLoading = false;
|
||||||
|
private genresLoading = false;
|
||||||
|
|
||||||
private coverSizeValue: number = COVER_SIZE_DEFAULT;
|
private coverSizeValue: number = COVER_SIZE_DEFAULT;
|
||||||
|
|
||||||
@@ -50,6 +53,7 @@ class LibraryStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.loadCoverSize();
|
this.loadCoverSize();
|
||||||
|
this.eagerFetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@@ -126,6 +130,29 @@ class LibraryStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getGenres(): Promise<library.GenreWithCount[]> {
|
||||||
|
if (this.genres !== null) {
|
||||||
|
return this.genres;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.genresLoading) {
|
||||||
|
return this.waitForGenres();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.genresLoading = true;
|
||||||
|
this.notify();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const genres = await GetAllGenresWithCounts();
|
||||||
|
this.genres = genres;
|
||||||
|
|
||||||
|
return genres;
|
||||||
|
} finally {
|
||||||
|
this.genresLoading = false;
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getAlbumsByArtist(
|
async getAlbumsByArtist(
|
||||||
artistID: number,
|
artistID: number,
|
||||||
): Promise<library.Album[]> {
|
): Promise<library.Album[]> {
|
||||||
@@ -179,6 +206,14 @@ class LibraryStore {
|
|||||||
return this.artistsLoading;
|
return this.artistsLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCachedGenres(): library.GenreWithCount[] | null {
|
||||||
|
return this.genres;
|
||||||
|
}
|
||||||
|
|
||||||
|
isGenresLoading(): boolean {
|
||||||
|
return this.genresLoading;
|
||||||
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// SCROLL POSITION
|
// SCROLL POSITION
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@@ -249,21 +284,24 @@ class LibraryStore {
|
|||||||
this.tracks = null;
|
this.tracks = null;
|
||||||
this.albums = null;
|
this.albums = null;
|
||||||
this.artists = null;
|
this.artists = null;
|
||||||
|
this.genres = null;
|
||||||
this.scrollPositions = { tracks: 0, albums: 0, artists: 0, genres: 0 };
|
this.scrollPositions = { tracks: 0, albums: 0, artists: 0, genres: 0 };
|
||||||
this.notify();
|
this.notify();
|
||||||
this.eagerRefetch();
|
this.eagerFetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-fetches all data after cache invalidation so that
|
* Fetches all library data. Called from the constructor
|
||||||
|
* (initial load) and after cache invalidation so that
|
||||||
* controller subscribers receive fresh data on the next
|
* controller subscribers receive fresh data on the next
|
||||||
* requestUpdate() cycle without needing their own
|
* requestUpdate() cycle without needing their own
|
||||||
* LibraryScanComplete listener.
|
* LibraryScanComplete listener.
|
||||||
*/
|
*/
|
||||||
private eagerRefetch(): void {
|
private eagerFetch(): void {
|
||||||
void this.getTracks();
|
void this.getTracks();
|
||||||
void this.getAlbums();
|
void this.getAlbums();
|
||||||
void this.getArtists();
|
void this.getArtists();
|
||||||
|
void this.getGenres();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@@ -317,6 +355,17 @@ class LibraryStore {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private waitForGenres(): Promise<library.GenreWithCount[]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const unsub = this.subscribe(() => {
|
||||||
|
if (!this.genresLoading && this.genres !== null) {
|
||||||
|
unsub();
|
||||||
|
resolve(this.genres);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton instance.
|
// Singleton instance.
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ class PlaylistStore {
|
|||||||
EventsOn(Events.PlaylistsRestored, () => {
|
EventsOn(Events.PlaylistsRestored, () => {
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void this.getPlaylists();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user