A music player that connects to your cloud/distributed storage.
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};