added db query optimizations to playlists view, added caching

This commit is contained in:
2026-02-17 11:39:17 -05:00
parent 93679761af
commit 50718fd633
13 changed files with 542 additions and 110 deletions
@@ -0,0 +1,75 @@
import type { ReactiveController, ReactiveControllerHost } from 'lit';
import type { playlist } from '@go/models';
import { playlistStore } from '../playlist-store';
/**
* PlaylistController connects a Lit component to the PlaylistStore.
*
* Usage in a component:
*
* private playlistCtrl = new PlaylistController(this);
*
* async connectedCallback() {
* super.connectedCallback();
* const playlists = await this.playlistCtrl.getPlaylists();
* }
*/
export class PlaylistController implements ReactiveController {
private host: ReactiveControllerHost;
private unsubscribe?: () => void;
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
// ===================================================================
// LIFECYCLE HOOKS
// ===================================================================
hostConnected(): void {
this.unsubscribe = playlistStore.subscribe(() => {
this.host.requestUpdate();
});
}
hostDisconnected(): void {
this.unsubscribe?.();
}
// ===================================================================
// DATA ACCESS
// ===================================================================
async getPlaylists(): Promise<playlist.WithTracks[]> {
return playlistStore.getPlaylists();
}
get cachedPlaylists(): playlist.WithTracks[] | null {
return playlistStore.getCachedPlaylists();
}
get isLoading(): boolean {
return playlistStore.isLoading();
}
// ===================================================================
// SCROLL POSITION
// ===================================================================
getScrollPosition(): number {
return playlistStore.getScrollPosition();
}
setScrollPosition(offset: number): void {
playlistStore.setScrollPosition(offset);
}
// ===================================================================
// INVALIDATION
// ===================================================================
invalidate(): void {
playlistStore.invalidate();
}
}
+110
View File
@@ -0,0 +1,110 @@
import { GetAllPlaylistsWithTracks } from '@go/playlist/Service';
import type { playlist } from '@go/models';
type Subscriber = () => void;
class PlaylistStore {
private playlists: playlist.WithTracks[] | null = null;
private playlistsLoading = false;
private scrollPosition = 0;
private subscribers = new Set<Subscriber>();
// ===================================================================
// DATA ACCESS
// Returns cached data or fetches from backend on first access.
// ===================================================================
async getPlaylists(): Promise<playlist.WithTracks[]> {
if (this.playlists !== null) {
return this.playlists;
}
if (this.playlistsLoading) {
return this.waitForPlaylists();
}
this.playlistsLoading = true;
this.notify();
try {
const result = await GetAllPlaylistsWithTracks();
this.playlists = result ?? [];
return this.playlists;
} finally {
this.playlistsLoading = false;
this.notify();
}
}
// ===================================================================
// STATE ACCESSORS
// Synchronous access for controllers that need current cached values.
// ===================================================================
getCachedPlaylists(): playlist.WithTracks[] | null {
return this.playlists;
}
isLoading(): boolean {
return this.playlistsLoading;
}
// ===================================================================
// SCROLL POSITION
// ===================================================================
getScrollPosition(): number {
return this.scrollPosition;
}
setScrollPosition(offset: number): void {
this.scrollPosition = offset;
}
// ===================================================================
// INVALIDATION
// ===================================================================
invalidate(): void {
this.playlists = null;
this.scrollPosition = 0;
this.notify();
}
// ===================================================================
// SUBSCRIPTION SYSTEM
// ===================================================================
subscribe(callback: Subscriber): () => void {
this.subscribers.add(callback);
return () => this.subscribers.delete(callback);
}
private notify(): void {
this.subscribers.forEach((callback) => callback());
}
// ===================================================================
// HELPERS
// Wait for an in-flight fetch to complete.
// ===================================================================
private waitForPlaylists(): Promise<playlist.WithTracks[]> {
return new Promise((resolve) => {
const unsub = this.subscribe(() => {
if (
!this.playlistsLoading &&
this.playlists !== null
) {
unsub();
resolve(this.playlists);
}
});
});
}
}
// Singleton instance.
export const playlistStore = new PlaylistStore();