A music player that connects to your cloud/distributed storage.
at v4 133 lines 3.5 kB view raw
1import foundation from "~/common/foundation.js"; 2import { effect } from "~/common/signal.js"; 3import * as Playlist from "~/common/playlist.js"; 4 5// Set doc title 6document.title = "Automatic Queue | Diffuse"; 7 8const ACTIVE_CLASS = "button--active"; 9 10// Setup 11await foundation.orchestrator.autoQueue(); 12 13const [repeatShuffle, scope, output] = await Promise.all([ 14 foundation.engine.repeatShuffle(), 15 foundation.engine.scope(), 16 foundation.orchestrator.output(), 17]); 18 19// Elements 20const repeatBtn = 21 /** @type {HTMLButtonElement} */ (document.querySelector("#repeat")); 22const shuffleBtn = 23 /** @type {HTMLButtonElement} */ (document.querySelector("#shuffle")); 24const searchInput = 25 /** @type {HTMLInputElement} */ (document.querySelector("#search")); 26const playlistSelect = 27 /** @type {HTMLSelectElement} */ (document.querySelector("#playlist")); 28const sortBySelect = 29 /** @type {HTMLSelectElement} */ (document.querySelector("#sort-by")); 30const sortDirectionBtn = 31 /** @type {HTMLButtonElement} */ (document.querySelector("#sort-direction")); 32 33// Repeat & Shuffle state 34effect(() => { 35 repeatBtn.classList.toggle(ACTIVE_CLASS, repeatShuffle.repeat()); 36}); 37 38effect(() => { 39 shuffleBtn.classList.toggle(ACTIVE_CLASS, repeatShuffle.shuffle()); 40}); 41 42// Actions 43repeatBtn.onclick = () => { 44 repeatShuffle.setRepeat(!repeatShuffle.repeat()); 45}; 46 47shuffleBtn.onclick = () => { 48 repeatShuffle.setShuffle(!repeatShuffle.shuffle()); 49}; 50 51// Search state 52effect(() => { 53 searchInput.value = scope.searchTerm() ?? ""; 54}); 55 56searchInput.oninput = () => { 57 scope.setSearchTerm(searchInput.value.trim() || undefined); 58}; 59 60// Playlist state 61effect(() => { 62 const col = output.playlistItems.collection(); 63 const items = col.state === "loaded" ? col.data : []; 64 const currentPlaylist = scope.playlist(); 65 66 // Group items by playlist name 67 const playlistMap = Playlist.gather(items); 68 const all = [...playlistMap.values()].sort((a, b) => 69 a.name.localeCompare(b.name) 70 ); 71 72 const ordered = all.filter((p) => !p.unordered); 73 const unordered = all.filter((p) => p.unordered); 74 75 playlistSelect.innerHTML = `<option value="">All tracks</option>`; 76 77 for ( 78 const [label, group] of [ 79 ["Ordered", ordered], 80 ["Unordered", unordered], 81 ] 82 ) { 83 if (group.length === 0) continue; 84 85 const optgroup = document.createElement("optgroup"); 86 optgroup.label = /** @type {string} */ (label); 87 88 for (const playlist of group) { 89 if (typeof playlist === "string") continue; 90 const option = document.createElement("option"); 91 92 option.value = playlist.name; 93 option.textContent = playlist.name; 94 option.selected = playlist.name === currentPlaylist; 95 96 optgroup.appendChild(option); 97 } 98 99 playlistSelect.appendChild(optgroup); 100 } 101}); 102 103playlistSelect.onchange = () => { 104 scope.setPlaylist( 105 playlistSelect.value.length ? playlistSelect.value : undefined, 106 ); 107}; 108 109// Sort by state 110effect(() => { 111 const current = JSON.stringify(scope.sortBy()); 112 for (const option of sortBySelect.options) { 113 if (JSON.stringify(JSON.parse(option.value)) === current) { 114 sortBySelect.value = option.value; 115 break; 116 } 117 } 118}); 119 120sortBySelect.onchange = () => { 121 scope.setSortBy(JSON.parse(sortBySelect.value)); 122}; 123 124// Sort direction state 125effect(() => { 126 const dir = scope.sortDirection() ?? "desc"; 127 sortDirectionBtn.textContent = dir.toUpperCase(); 128}); 129 130sortDirectionBtn.onclick = () => { 131 const dir = scope.sortDirection() ?? "desc"; 132 scope.setSortDirection(dir === "asc" ? "desc" : "asc"); 133};