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