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 184 lines 5.3 kB view raw
1import * as Output from "~/common/output.js"; 2import foundation from "~/common/foundation.js"; 3import { effect } from "~/common/signal.js"; 4 5// Set doc title 6foundation.setup({ title: "Export & Import | Diffuse" }); 7 8// Setup 9const main = /** @type {HTMLElement} */ (document.querySelector("main")); 10const output = await foundation.orchestrator.output(); 11 12// Elements 13const exportBtn = 14 /** @type {HTMLButtonElement} */ (document.querySelector("#export")); 15const fileInput = 16 /** @type {HTMLInputElement} */ (document.querySelector("#file")); 17const importTracksBtn = 18 /** @type {HTMLButtonElement} */ (document.querySelector("#import-tracks")); 19const importPlaylistItemsBtn = 20 /** @type {HTMLButtonElement} */ (document.querySelector( 21 "#import-playlist-items", 22 )); 23const importFacetsBtn = 24 /** @type {HTMLButtonElement} */ (document.querySelector("#import-facets")); 25const statusEl = /** @type {HTMLElement} */ (document.querySelector("#status")); 26 27/** @type {Record<string, any> | null} */ 28let json = null; 29 30/** 31 * Show a status message. 32 * @param {string} message 33 * @param {"success" | "error"} type 34 */ 35function showStatus(message, type) { 36 statusEl.textContent = message; 37 statusEl.className = `status status--${type}`; 38 statusEl.hidden = false; 39} 40 41// Enable export button once output is ready 42effect(() => { 43 exportBtn.disabled = !output.ready(); 44}); 45 46// Export all data as a JSON snapshot 47exportBtn.onclick = async () => { 48 const facets = await Output.data(output.facets); 49 const playlistItems = await Output.data(output.playlistItems); 50 const tracks = await Output.data(output.tracks); 51 52 const data = { 53 exportedAt: new Date().toISOString(), 54 facets, 55 playlistItems, 56 tracks, 57 }; 58 59 const blob = new Blob([JSON.stringify(data, null, 2)], { 60 type: "application/json", 61 }); 62 63 const url = URL.createObjectURL(blob); 64 const a = document.createElement("a"); 65 a.href = url; 66 a.download = `diffuse-${new Date().toISOString().slice(0, 10)}.json`; 67 document.body.append(a); 68 a.click(); 69 a.remove(); 70 71 URL.revokeObjectURL(url); 72}; 73 74// Parse file on selection 75fileInput.onchange = async () => { 76 const file = fileInput.files?.[0]; 77 78 json = null; 79 statusEl.hidden = true; 80 importTracksBtn.disabled = true; 81 importPlaylistItemsBtn.disabled = true; 82 importFacetsBtn.disabled = true; 83 84 if (!file) return; 85 86 try { 87 json = JSON.parse(await file.text()); 88 } catch (err) { 89 console.error("Failed to parse JSON:", err); 90 showStatus( 91 `Failed to parse JSON: ${/** @type {Error} */ (err).message}`, 92 "error", 93 ); 94 return; 95 } 96 97 if (Array.isArray(json?.tracks) && json.tracks.length > 0) { 98 importTracksBtn.disabled = false; 99 } 100 101 if (Array.isArray(json?.playlistItems) && json.playlistItems.length > 0) { 102 importPlaylistItemsBtn.disabled = false; 103 } 104 105 if (Array.isArray(json?.facets) && json.facets.length > 0) { 106 importFacetsBtn.disabled = false; 107 } 108}; 109 110/** 111 * @param {HTMLButtonElement} btn 112 * @param {string} label 113 */ 114function setButtonLabel(btn, label) { 115 const span = /** @type {HTMLElement} */ (btn.querySelector("span")); 116 span.textContent = label; 117} 118 119// Import tracks 120importTracksBtn.onclick = async () => { 121 /** @type {any[]} */ 122 const tracks = json?.tracks; 123 if (!Array.isArray(tracks) || tracks.length === 0) return; 124 125 importTracksBtn.disabled = true; 126 setButtonLabel(importTracksBtn, " Importing ..."); 127 try { 128 await output.tracks.save(tracks); 129 showStatus(`Imported ${tracks.length} track(s).`, "success"); 130 } catch (err) { 131 console.error("Import failed:", err); 132 showStatus(`Import failed: ${/** @type {Error} */ (err).message}`, "error"); 133 } finally { 134 setButtonLabel(importTracksBtn, " Import tracks"); 135 importTracksBtn.disabled = false; 136 } 137}; 138 139// Import playlist items 140importPlaylistItemsBtn.onclick = async () => { 141 /** @type {any[]} */ 142 const playlistItems = json?.playlistItems; 143 if (!Array.isArray(playlistItems) || playlistItems.length === 0) return; 144 145 importPlaylistItemsBtn.disabled = true; 146 setButtonLabel(importPlaylistItemsBtn, " Importing ..."); 147 try { 148 await output.playlistItems.save(playlistItems); 149 const playlistCount = new Set(playlistItems.map((p) => p.playlist)).size; 150 showStatus(`Imported ${playlistCount} playlist(s).`, "success"); 151 } catch (err) { 152 console.error("Import failed:", err); 153 showStatus(`Import failed: ${/** @type {Error} */ (err).message}`, "error"); 154 } finally { 155 setButtonLabel(importPlaylistItemsBtn, " Import playlist items"); 156 importPlaylistItemsBtn.disabled = false; 157 } 158}; 159 160// Import facets 161importFacetsBtn.onclick = async () => { 162 /** @type {any[]} */ 163 const facets = json?.facets; 164 if (!Array.isArray(facets) || facets.length === 0) return; 165 166 importFacetsBtn.disabled = true; 167 setButtonLabel(importFacetsBtn, " Importing ..."); 168 try { 169 await output.facets.save(facets); 170 showStatus(`Imported ${facets.length} facet(s).`, "success"); 171 } catch (err) { 172 console.error("Import failed:", err); 173 showStatus(`Import failed: ${/** @type {Error} */ (err).message}`, "error"); 174 } finally { 175 setButtonLabel(importFacetsBtn, " Import facets"); 176 importFacetsBtn.disabled = false; 177 } 178}; 179 180//////////////////////////////////////////// 181// 🚀 182//////////////////////////////////////////// 183 184foundation.ready();