A music player that connects to your cloud/distributed storage.
1import * as Output from "~/common/output.js";
2import { facetFromURI } from "~/common/facets/utils.js";
3import { effect } from "~/common/signal.js";
4
5import { output } from "./output.js";
6
7////////////////////////////////////////////
8// FILTER
9////////////////////////////////////////////
10
11export function setupFilter() {
12 /** @type {NodeListOf<HTMLElement>} */
13 const buttons = document.querySelectorAll(".grid-filter button");
14
15 /** @type {NodeListOf<HTMLElement>} */
16 const items = document.querySelectorAll(".grid-item");
17
18 buttons.forEach((b) => {
19 b.addEventListener("click", () => {
20 buttons.forEach((b) => b.classList.add("button--transparent"));
21 b.classList.remove("button--transparent");
22
23 const filter = b.dataset.filter;
24
25 items.forEach((item) => {
26 const kind = item.dataset.kind;
27 const show = filter === "all" || kind === filter;
28 item.hidden = !show;
29
30 /** @type {HTMLElement | null} */
31 const kindEl = item.querySelector(".grid-item__kind");
32 if (!kindEl) return;
33 kindEl.hidden = filter !== "all";
34 });
35 });
36 });
37}
38
39////////////////////////////////////////////
40// OUTPUT INDICATOR
41////////////////////////////////////////////
42
43/** @type {() => void | undefined} */
44let stopOutputIndicator;
45
46export async function setupOutputIndicator() {
47 if (stopOutputIndicator) stopOutputIndicator();
48
49 const filterEl = document.querySelector(".grid-filter");
50 if (!filterEl) return;
51
52 const out = await output();
53
54 /** @type {HTMLElement | null} */
55 const indicator = filterEl.querySelector(".grid-filter--output");
56 if (!indicator) return;
57
58 /** @type {HTMLElement | null} */
59 const label = filterEl.querySelector(".grid-filter--label-output");
60 if (!label) return;
61
62 setTimeout(() => {
63 indicator.style.opacity = "1";
64 label.style.opacity = "0.4";
65 }, 250);
66
67 stopOutputIndicator = effect(() => {
68 const selected = out.selected();
69 const label = selected?.label ?? selected?.getAttribute?.("label") ??
70 "Local storage";
71 indicator.textContent = label;
72 });
73}
74
75////////////////////////////////////////////
76// TOGGLE BUTTONS
77////////////////////////////////////////////
78
79export function insertToggleButtons() {
80 const gridItems = /** @type {NodeListOf<HTMLLIElement>} */ (
81 document.querySelectorAll(".grid li")
82 );
83
84 for (const li of gridItems) {
85 const container = li.querySelector(".grid-item__title");
86 if (!container) continue;
87
88 const button = document.createElement("button");
89 button.className = "button--transparent";
90 button.style.cssText =
91 `color: oklch(from currentColor l c h / 0.4); font-size: var(--fs-md); opacity: 0; padding: 0;`;
92 button.innerHTML = `<i class="ph-fill ph-toggle-left"></i>`;
93
94 button.addEventListener("click", async (event) => {
95 event.preventDefault();
96
97 const uri = li.getAttribute("data-uri");
98 const name = li.getAttribute("data-name");
99 const kind = li.getAttribute("data-kind") ?? undefined;
100 const description = li.getAttribute("data-description") ?? undefined;
101
102 if (!uri || !name) return;
103
104 const out = await output();
105 const collection = await Output.data(out.facets);
106 const isActive = collection.some((f) =>
107 f.uri === uri && f.html === undefined
108 );
109
110 if (isActive) {
111 out.facets.save(collection.filter((f) => f.uri !== uri));
112 } else {
113 const facet = await facetFromURI({ description, kind, name, uri }, {
114 fetchHTML: false,
115 });
116 out.facets.save([...collection, facet]);
117 }
118 });
119
120 container.appendChild(button);
121 }
122}
123
124////////////////////////////////////////////
125// SYNC ACTIVE STATES
126////////////////////////////////////////////
127
128/** @type {() => void | undefined} */
129let stopMonitor;
130
131export async function monitorToggleButtonStates() {
132 if (stopMonitor) stopMonitor();
133 const out = await output();
134
135 stopMonitor = effect(() => {
136 const gridItems = /** @type {NodeListOf<HTMLLIElement>} */ (
137 document.querySelectorAll(".grid li")
138 );
139
140 const col = out.facets.collection();
141 const collection = col.state === "loaded" ? col.data : [];
142 const colMap = new Map(collection.map((f) => [f.uri, f]));
143
144 for (const li of gridItems) {
145 const uri = li.getAttribute("data-uri");
146 const button =
147 /** @type {HTMLElement | null} */ (li.querySelector("button"));
148 const icon = button?.querySelector("i");
149
150 if (!button || !icon || !uri) continue;
151
152 button.style.opacity = "revert-layer";
153
154 const item = colMap.get(uri);
155 const isActive = item && item.html === undefined;
156
157 button.title = isActive
158 ? "Remove from your collection"
159 : "Add to your collection";
160 icon.className = isActive
161 ? "ph-fill ph-toggle-right"
162 : "ph-fill ph-toggle-left";
163 /** @type {HTMLElement} */ (icon).style.color = isActive
164 ? li.dataset.activeColor ?? "var(--accent-twist-2)"
165 : "";
166 }
167 });
168}