small ui changes

This commit is contained in:
2026-02-20 15:22:49 -05:00
parent 88c3736110
commit 84f027aedf
5 changed files with 105 additions and 36 deletions
+1 -1
View File
@@ -1 +1 @@
cf76bbfd46ad4447fbfbaa4a1c6845ca
db9e9335c200a37f58ae820ffcfee304
-21
View File
@@ -17,9 +17,6 @@ importers:
'@lit-labs/virtualizer':
specifier: ^2.1.1
version: 2.1.1
htmx.org:
specifier: 2.0.8
version: 2.0.8
lit:
specifier: ^3.2.1
version: 3.3.2
@@ -350,79 +347,66 @@ packages:
resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.57.1':
resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.57.1':
resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.57.1':
resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.57.1':
resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.57.1':
resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.57.1':
resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.57.1':
resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.57.1':
resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.57.1':
resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.57.1':
resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.57.1':
resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.57.1':
resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.57.1':
resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==}
@@ -709,9 +693,6 @@ packages:
resolution: {integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==}
engines: {node: '>=20.10'}
htmx.org@2.0.8:
resolution: {integrity: sha512-fm297iru0iWsNJlBrjvtN7V9zjaxd+69Oqjh4F/Vq9Wwi2kFisLcrLCiv5oBX0KLfOX/zG8AUo9ROMU5XUB44Q==}
ignore@7.0.5:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
@@ -1685,8 +1666,6 @@ snapshots:
html-tags@5.1.0: {}
htmx.org@2.0.8: {}
ignore@7.0.5: {}
import-fresh@3.3.1:
@@ -67,6 +67,7 @@ export class PlaylistView
@state() private entries: PlaylistEntry[] = [];
@state() private loading = true;
@state() private refreshing = false;
@state() private creating = false;
@state() private newPlaylistName = '';
@state() private contextMenuOpen = false;
@@ -213,6 +214,25 @@ export class PlaylistView
font-size: 18px;
font-weight: 600;
color: var(--yj-text-primary, #fff);
display: flex;
align-items: center;
gap: 10px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.header-spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid var(--yj-border-subtle, #555);
border-top-color: var(--yj-text-primary, #fff);
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
.new-playlist-button {
@@ -644,6 +664,41 @@ export class PlaylistView
this.restoreScrollPosition();
}
/**
* Re-fetches playlists without clearing the current view.
* Shows a spinner in the header while the fetch is in-flight
* and preserves the expanded/collapsed state of each playlist.
*/
private async refreshPlaylists() {
this.refreshing = true;
try {
const playlists =
await this.playlistCtrl.refetch();
const expandedIDs = new Set(
this.entries
.filter((e) => e.expanded)
.map((e) => e.summary.ID),
);
this.entries = playlists.map((p) => ({
summary: p.Summary,
expanded: expandedIDs.has(
p.Summary.ID,
),
tracks: p.Tracks ?? [],
}));
} catch (err) {
console.error(
'Failed to refresh playlists:',
err,
);
} finally {
this.refreshing = false;
}
}
private handleToggle = (index: number) => {
const entry = this.entries[index];
@@ -796,8 +851,7 @@ export class PlaylistView
trackIDs,
);
this.playlistCtrl.invalidate();
await this.loadPlaylists();
await this.refreshPlaylists();
} catch (err) {
console.error(
'Failed to remove tracks:',
@@ -954,8 +1008,7 @@ export class PlaylistView
entry.summary.ID,
payload.filePaths,
);
this.playlistCtrl.invalidate();
await this.loadPlaylists();
await this.refreshPlaylists();
} catch (err) {
console.error(
'Failed to add tracks to playlist:',
@@ -1127,8 +1180,7 @@ export class PlaylistView
) {
try {
await DeletePlaylist(playlistID);
this.playlistCtrl.invalidate();
await this.loadPlaylists();
await this.refreshPlaylists();
} catch (err) {
console.error(
'Failed to delete playlist:',
@@ -1183,8 +1235,7 @@ export class PlaylistView
entry.summary.ID,
trimmed,
);
this.playlistCtrl.invalidate();
await this.loadPlaylists();
await this.refreshPlaylists();
} catch (err) {
console.error(
'Failed to rename playlist:',
@@ -1205,8 +1256,7 @@ export class PlaylistView
if (!filePath) return;
await ImportPlaylist(filePath);
this.playlistCtrl.invalidate();
await this.loadPlaylists();
await this.refreshPlaylists();
} catch (err) {
console.error(
'Failed to import playlist:',
@@ -1246,8 +1296,7 @@ export class PlaylistView
await CreatePlaylist(name);
this.creating = false;
this.newPlaylistName = '';
this.playlistCtrl.invalidate();
await this.loadPlaylists();
await this.refreshPlaylists();
} catch (err) {
console.error(
'Failed to create playlist:',
@@ -1276,7 +1325,14 @@ export class PlaylistView
override render() {
return html`
<div class="header">
<h2>Playlists</h2>
<h2>
Playlists
${this.refreshing
? html`<span
class="header-spinner"
></span>`
: nothing}
</h2>
<div
style="display: flex; gap: 8px;"
>
@@ -1306,7 +1362,8 @@ export class PlaylistView
${this.creating
? this.renderCreateForm()
: nothing}
${this.loading
${this.loading &&
this.entries.length === 0
? html`<div class="loading">
Loading playlists...
</div>`
@@ -65,6 +65,14 @@ export class PlaylistController implements ReactiveController {
playlistStore.setScrollPosition(offset);
}
// ===================================================================
// REFETCH
// ===================================================================
async refetch(): Promise<playlist.WithTracks[]> {
return playlistStore.refetch();
}
// ===================================================================
// INVALIDATION
// ===================================================================
+25
View File
@@ -90,6 +90,31 @@ class PlaylistStore {
this.scrollPosition = offset;
}
// ===================================================================
// REFETCH
// Fetches fresh data without clearing the cache first, so existing
// consumers keep rendering stale data until the new data arrives.
// ===================================================================
async refetch(): Promise<playlist.WithTracks[]> {
if (this.playlistsLoading) {
return this.waitForPlaylists();
}
this.playlistsLoading = true;
try {
const result =
await GetAllPlaylistsWithTracks();
this.playlists = result ?? [];
return this.playlists;
} finally {
this.playlistsLoading = false;
this.notify();
}
}
// ===================================================================
// INVALIDATION
// ===================================================================