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