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

Configure Feed

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

at v4 205 lines 7.1 kB view raw
1import { html, render as litRender } from "lit-html"; 2 3import * as Output from "~/common/output.js"; 4import foundation from "~/common/foundation.js"; 5import { effect } from "~/common/signal.js"; 6 7import { SCHEME as SCHEME_DROPBOX } from "~/components/input/dropbox/constants.js"; 8import { SCHEME as SCHEME_EPHEMERAL_CACHE } from "~/components/input/ephemeral-cache/constants.js"; 9import { SCHEME as SCHEME_HTTPS } from "~/components/input/https/constants.js"; 10import { SCHEME as SCHEME_ICECAST } from "~/components/input/icecast/constants.js"; 11import { SCHEME as SCHEME_LOCAL } from "~/components/input/local/constants.js"; 12import { SCHEME as SCHEME_OPENSUBSONIC } from "~/components/input/opensubsonic/constants.js"; 13import { SCHEME as SCHEME_S3 } from "~/components/input/s3/constants.js"; 14 15/** @type {Record<string, string>} */ 16const SCHEME_NAMES = { 17 [SCHEME_DROPBOX]: "Dropbox", 18 [SCHEME_EPHEMERAL_CACHE]: "Browser storage", 19 [SCHEME_HTTPS]: "HTTPS", 20 [SCHEME_ICECAST]: "Icecast", 21 [SCHEME_LOCAL]: "Local directories & files", 22 [SCHEME_OPENSUBSONIC]: "OpenSubsonic", 23 [SCHEME_S3]: "S3", 24}; 25 26foundation.setup({ title: "Sources | Diffuse" }); 27 28//////////////////////////////////////////// 29// SETUP 30//////////////////////////////////////////// 31 32const [inputConfigurator, sourcesOrchestrator, outputOrchestrator, processOrchestrator] = 33 await Promise.all([ 34 foundation.configurator.input(), 35 foundation.orchestrator.sources(), 36 foundation.orchestrator.output(), 37 foundation.orchestrator.processTracks({ disableWhenReady: true }), 38 ]); 39 40await Promise.all([ 41 customElements.whenDefined(inputConfigurator.localName), 42 customElements.whenDefined(sourcesOrchestrator.localName), 43 customElements.whenDefined(outputOrchestrator.localName), 44]); 45 46 47//////////////////////////////////////////// 48// PROCESS BUTTON 49//////////////////////////////////////////// 50 51const processBtn = /** @type {HTMLButtonElement} */ (document.querySelector("#process-btn")); 52const processIcon = /** @type {HTMLElement} */ (document.querySelector("#process-icon")); 53const processLabel = /** @type {HTMLElement} */ (document.querySelector("#process-label")); 54 55effect(() => { 56 const isProcessing = processOrchestrator.isProcessing(); 57 58 processBtn.disabled = isProcessing; 59 processIcon.className = isProcessing 60 ? "ph-fill ph-arrows-clockwise animate-spin" 61 : "ph-fill ph-arrows-clockwise"; 62 processLabel.textContent = isProcessing ? "Processing" : "Process"; 63}); 64 65processBtn.addEventListener("click", async () => { 66 const output = await foundation.orchestrator.output(); 67 await Output.data(output.tracks); 68 await processOrchestrator.process(); 69}); 70 71//////////////////////////////////////////// 72// UI 73//////////////////////////////////////////// 74 75const list = 76 /** @type {HTMLElement} */ (document.querySelector("#sources-list")); 77const empty = 78 /** @type {HTMLElement} */ (document.querySelector("#sources-empty")); 79 80/** @param {string} uri */ 81const trackPrefix = (uri) => { const q = uri.indexOf("?"); return q === -1 ? uri : uri.slice(0, q); }; 82 83effect(() => { 84 const sourcesRecord = sourcesOrchestrator.sources(); 85 86 const tracksCol = outputOrchestrator.tracks.collection(); 87 const tracks = tracksCol.state === "loaded" ? tracksCol.data : []; 88 89 const entries = Object.entries(sourcesRecord).filter( 90 ([, sources]) => sources.length > 0, 91 ); 92 93 list.hidden = entries.length === 0; 94 empty.hidden = entries.length > 0; 95 96 litRender( 97 html` 98 ${entries.map(([scheme, sources]) => { 99 if (scheme === SCHEME_EPHEMERAL_CACHE) { 100 const uri = `${SCHEME_EPHEMERAL_CACHE}://`; 101 const isDisabled = sourcesOrchestrator.isDisabled(uri); 102 const trackCount = tracks.filter((t) => 103 t.uri.startsWith(uri) 104 ).length; 105 return html` 106 <li class="sources-scheme">${SCHEME_NAMES[scheme] ?? scheme}</li> 107 <li class="sources-item ${isDisabled 108 ? "sources-item--disabled" 109 : ""}"> 110 <div class="sources-item__info"> 111 <span class="sources-item__name">Files stored in the browser</span> 112 <span class="sources-item__detail">${trackCount} track${trackCount === 113 1 114 ? "" 115 : "s"}</span> 116 </div> 117 <button 118 class="button--plain" 119 title="${isDisabled ? "Enable source" : "Disable source"}" 120 @click="${() => sourcesOrchestrator.toggle(uri)}" 121 > 122 <i class="ph-fill ${isDisabled 123 ? "ph-eye-slash" 124 : "ph-eye"}"></i> 125 </button> 126 <button 127 class="button--plain button--icon" 128 title="Remove source" 129 @click="${() => removeEphemeralSources()}" 130 > 131 <i class="ph-fill ph-skull"></i> 132 </button> 133 </li> 134 `; 135 } 136 137 return html` 138 <li class="sources-scheme">${SCHEME_NAMES[scheme] ?? scheme}</li> 139 ${sources.map(({ label, uri }) => { 140 const isDisabled = sourcesOrchestrator.isDisabled(uri); 141 const trackCount = tracks.filter((t) => 142 t.uri.startsWith(trackPrefix(uri)) 143 ).length; 144 return html` 145 <li class="sources-item ${isDisabled 146 ? "sources-item--disabled" 147 : ""}"> 148 <div class="sources-item__info"> 149 <span class="sources-item__name">${label}</span> 150 <span class="sources-item__detail">${trackCount} track${trackCount === 151 1 152 ? "" 153 : "s"}</span> 154 </div> 155 <button 156 class="button--plain button--icon" 157 title="${isDisabled ? "Enable source" : "Disable source"}" 158 @click="${() => sourcesOrchestrator.toggle(uri)}" 159 > 160 <i class="ph-fill ${isDisabled 161 ? "ph-eye-slash" 162 : "ph-eye"}"></i> 163 </button> 164 <button 165 class="button--plain button--icon" 166 title="Remove source" 167 @click="${() => removeSource(uri)}" 168 > 169 <i class="ph-fill ph-skull"></i> 170 </button> 171 </li> 172 `; 173 })} 174 `; 175 })} 176 `, 177 list, 178 ); 179}); 180 181//////////////////////////////////////////// 182// ACTIONS 183//////////////////////////////////////////// 184 185async function removeEphemeralSources() { 186 return removeSource(SCHEME_EPHEMERAL_CACHE); 187} 188 189/** @param {string} uri */ 190async function removeSource(uri) { 191 const tracks = await Output.data(outputOrchestrator.tracks); 192 193 const detachedTracks = await inputConfigurator.detach({ 194 fileUriOrScheme: uri, 195 tracks, 196 }); 197 198 if (detachedTracks) await outputOrchestrator.tracks.save(detachedTracks); 199} 200 201//////////////////////////////////////////// 202// 🚀 203//////////////////////////////////////////// 204 205foundation.ready();