A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: optimise grid perf

+62 -43
+32 -31
src/components/orchestrator/cover-groups/element.js
··· 22 22 // STATE 23 23 24 24 artistGroups = computed(() => { 25 - const groups = /** @type {any} */ (this.#provider.value)?.groups?.(); 26 - const allTracks = this.#provider.value?.tracks() ?? []; 27 - 28 - // Total track counts per artist across all groups 29 - /** @type {Map<string, number>} */ 30 - const totalCounts = new Map(); 31 - for (const track of allTracks) { 32 - const key = String(track.tags?.artist ?? "").toLowerCase(); 33 - totalCounts.set(key, (totalCounts.get(key) ?? 0) + 1); 34 - } 25 + const provider = this.#provider.value; 26 + const groups = /** @type {any} */ (provider)?.groups?.(); 35 27 36 28 /** @type {{ label: string; groups: ArtistGroup[] }[]} */ 37 29 const result = []; 38 30 39 31 if (groups?.length) { 32 + const allTracks = provider?.tracks() ?? []; 33 + 34 + // Total track counts per artist across all groups 35 + /** @type {Map<string, number>} */ 36 + const totalCounts = new Map(); 37 + for (const track of allTracks) { 38 + const key = String(track.tags?.artist ?? "").toLowerCase(); 39 + totalCounts.set(key, (totalCounts.get(key) ?? 0) + 1); 40 + } 41 + 40 42 for ( 41 43 const group 42 44 of /** @type {{ label: string; tracks: Track[] }[]} */ (groups) ··· 48 50 if (artists.length) result.push({ label: group.label, groups: artists }); 49 51 } 50 52 } else { 53 + const allTracks = provider?.tracks() ?? []; 51 54 const artists = deduplicateArtists(allTracks); 52 55 if (artists.length) result.push({ label: "", groups: artists }); 53 56 } ··· 56 59 }); 57 60 58 61 coverGroups = computed(() => { 59 - const groups = /** @type {any} */ (this.#provider.value)?.groups?.(); 62 + const provider = this.#provider.value; 63 + const groups = /** @type {any} */ (provider)?.groups?.(); 60 64 61 65 /** @type {{ label: string; groups: CoverGroup[] }[]} */ 62 66 const result = []; ··· 70 74 if (albums.length) result.push({ label: group.label, groups: albums }); 71 75 } 72 76 } else { 73 - const tracks = this.#provider.value?.tracks() ?? []; 77 + const tracks = provider?.tracks() ?? []; 74 78 const albums = deduplicateAlbums(tracks); 75 79 if (albums.length) result.push({ label: "", groups: albums }); 76 80 } ··· 113 117 * @returns {CoverGroup[]} 114 118 */ 115 119 function deduplicateAlbums(tracks) { 116 - const sorted = [...tracks].sort((a, b) => { 117 - const aAlbum = String(a.tags?.album ?? "").toLowerCase(); 118 - const bAlbum = String(b.tags?.album ?? "").toLowerCase(); 119 - return aAlbum.localeCompare(bAlbum); 120 - }); 121 - 122 120 /** @type {Map<string, { track: Track; artists: Set<string> }>} */ 123 121 const albumMap = new Map(); 124 122 125 - for (const track of sorted) { 123 + for (const track of tracks) { 126 124 const albumKey = String(track.tags?.album ?? "").toLowerCase(); 127 125 const existing = albumMap.get(albumKey); 128 126 if (existing) { ··· 135 133 } 136 134 } 137 135 138 - return [...albumMap.entries()].map(([albumKey, { track, artists }]) => ({ 139 - albumKey, 140 - albumName: track.tags?.album ?? "Unknown album", 141 - artist: artists.size > 1 ? "Various Artists" : /** @type {string} */ ([...artists][0]), 142 - track, 143 - })); 136 + return [...albumMap.entries()] 137 + .sort(([a], [b]) => a.localeCompare(b)) 138 + .map(([albumKey, { track, artists }]) => ({ 139 + albumKey, 140 + albumName: track.tags?.album ?? "Unknown album", 141 + artist: artists.size > 1 ? "Various Artists" : /** @type {string} */ (artists.values().next().value), 142 + track, 143 + })); 144 144 } 145 145 146 146 /** ··· 148 148 * @returns {ArtistGroup[]} 149 149 */ 150 150 function deduplicateArtists(tracks) { 151 - /** @type {Map<string, { artistName: string; tracks: Track[] }>} */ 151 + /** @type {Map<string, { artistName: string; count: number; track: Track }>} */ 152 152 const map = new Map(); 153 153 154 154 for (const track of tracks) { 155 155 const artistKey = String(track.tags?.artist ?? "").toLowerCase(); 156 156 const existing = map.get(artistKey); 157 157 if (existing) { 158 - existing.tracks.push(track); 158 + existing.count++; 159 159 } else { 160 160 map.set(artistKey, { 161 161 artistName: track.tags?.artist ?? "Unknown artist", 162 - tracks: [track], 162 + count: 1, 163 + track, 163 164 }); 164 165 } 165 166 } 166 167 167 168 return [...map.entries()] 168 169 .sort(([a], [b]) => a.localeCompare(b)) 169 - .map(([artistKey, { artistName, tracks }]) => ({ 170 + .map(([artistKey, { artistName, count, track }]) => ({ 170 171 artistKey, 171 172 artistName, 172 - trackCount: tracks.length, 173 - track: tracks[0], 173 + trackCount: count, 174 + track, 174 175 })); 175 176 } 176 177
+2
src/themes/blur/browser/element.css
··· 438 438 } 439 439 440 440 .cover-card { 441 + contain-intrinsic-size: auto 11rem; 442 + content-visibility: auto; 441 443 cursor: pointer; 442 444 display: flex; 443 445 flex-direction: column;
+28 -12
src/themes/blur/browser/element.js
··· 1 + import { 2 + elementScroll, 3 + observeElementOffset, 4 + observeElementRect, 5 + Virtualizer, 6 + } from "@tanstack/virtual-core"; 7 + 1 8 import { 2 9 defineElement, 3 10 DiffuseElement, ··· 7 14 } from "~/common/element.js"; 8 15 import { computed, signal, untracked } from "~/common/signal.js"; 9 16 import * as Playlist from "~/common/playlist.js"; 10 - import { 11 - elementScroll, 12 - observeElementOffset, 13 - observeElementRect, 14 - Virtualizer, 15 - } from "@tanstack/virtual-core"; 16 17 17 18 /** 18 19 * @import {RenderArg} from "~/common/element.d.ts" ··· 483 484 const sortDirection = this.$scope.value?.sortDirection() ?? "asc"; 484 485 485 486 const totalCount = coverViewMode === "artists" 486 - ? (this.$coverGroups.value?.artistGroups() ?? []).reduce((n, g) => n + g.groups.length, 0) 487 - : (this.$coverGroups.value?.coverGroups() ?? []).reduce((n, g) => n + g.groups.length, 0); 487 + ? (this.$coverGroups.value?.artistGroups() ?? []).reduce( 488 + (n, g) => n + g.groups.length, 489 + 0, 490 + ) 491 + : (this.$coverGroups.value?.coverGroups() ?? []).reduce( 492 + (n, g) => n + g.groups.length, 493 + 0, 494 + ); 488 495 489 496 const countLabel = coverViewMode === "artists" 490 497 ? `${totalCount} ${totalCount === 1 ? "artist" : "artists"}` ··· 519 526 @click="${() => { 520 527 const scope = this.$scope.value; 521 528 if (!scope) return; 522 - scope.setSortDirection(scope.sortDirection() === `asc` ? `desc` : `asc`); 529 + scope.setSortDirection( 530 + scope.sortDirection() === `asc` ? `desc` : `asc`, 531 + ); 523 532 }}" 524 - title="${sortDirection === `desc` ? `Sort ascending` : `Sort descending`}" 533 + title="${sortDirection === `desc` 534 + ? `Sort ascending` 535 + : `Sort descending`}" 525 536 > 526 - <i class="ph-bold ph-arrow-${sortDirection === `desc` ? `down` : `up`}"></i> 537 + <i class="ph-bold ph-arrow-${sortDirection === `desc` 538 + ? `down` 539 + : `up`}"></i> 527 540 </button> 528 541 </div> 529 542 </div> ··· 862 875 863 876 const allTracks = this.$provider.value?.tracks() ?? []; 864 877 865 - let key = "", name = "", subtitle = "", detailTracks = /** @type {Track[]} */ ([]); 878 + let key = "", 879 + name = "", 880 + subtitle = "", 881 + detailTracks = /** @type {Track[]} */ ([]); 866 882 867 883 if (item.type === "album") { 868 884 key = item.albumKey;