added db query optimizations to playlists view, added caching
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user