A music player that connects to your cloud/distributed storage.
at v4 166 lines 4.8 kB view raw
1import * as TID from "@atcute/tid"; 2import foundation from "~/common/foundation.js"; 3 4document.title = "V3.x Import | Diffuse"; 5 6const main = /** @type {HTMLElement} */ (document.querySelector("main")); 7main.classList.add("has-loaded"); 8 9/** 10 * @import {PlaylistItem, Track} from "~/definitions/types.d.ts" 11 */ 12 13// Setup 14const favourites = await foundation.orchestrator.favourites(); 15const output = await foundation.orchestrator.output(); 16 17// Elements 18const fileInput = 19 /** @type {HTMLInputElement} */ (document.querySelector("#file")); 20const importFavouritesBtn = 21 /** @type {HTMLButtonElement} */ (document.querySelector( 22 "#import-favourites", 23 )); 24const importPlaylistItemsBtn = 25 /** @type {HTMLButtonElement} */ (document.querySelector( 26 "#import-playlist-items", 27 )); 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// Parse file on selection 45fileInput.onchange = async () => { 46 const file = fileInput.files?.[0]; 47 48 json = null; 49 statusEl.hidden = true; 50 importFavouritesBtn.disabled = true; 51 importPlaylistItemsBtn.disabled = true; 52 53 if (!file) return; 54 55 try { 56 json = JSON.parse(await file.text()); 57 } catch (err) { 58 console.error("Failed to parse JSON:", err); 59 showStatus( 60 `Failed to parse JSON: ${/** @type {Error} */ (err).message}`, 61 "error", 62 ); 63 return; 64 } 65 66 if (json?.favourites?.data?.length > 0) { 67 importFavouritesBtn.disabled = false; 68 } 69 70 if (json?.playlists?.data?.length > 0) { 71 importPlaylistItemsBtn.disabled = false; 72 } 73}; 74 75// Import favourites on button click 76importFavouritesBtn.onclick = async () => { 77 /** @type {any[]} */ 78 const items = json?.favourites?.data; 79 if (!items || items.length === 0) return; 80 81 try { 82 /** @type {Track[]} */ 83 const tracks = items.map((item) => ({ 84 $type: "sh.diffuse.output.track", 85 id: "", 86 uri: "", 87 tags: { 88 artist: item.artist ?? "", 89 title: item.title ?? "", 90 }, 91 })); 92 93 await favourites.include(tracks); 94 showStatus(`Imported ${tracks.length} favourite(s).`, "success"); 95 } catch (err) { 96 console.error("Import failed:", err); 97 showStatus(`Import failed: ${/** @type {Error} */ (err).message}`, "error"); 98 } 99}; 100 101// Import playlist items on button click 102importPlaylistItemsBtn.onclick = async () => { 103 /** @type {any[]} */ 104 const items = json?.playlists?.data; 105 if (!items || items.length === 0) return; 106 107 try { 108 const now = new Date().toISOString(); 109 110 const existingCol = output.playlistItems.collection(); 111 /** @type {any[]} */ 112 const existing = existingCol.state === "loaded" ? existingCol.data : []; 113 const existingPlaylistNames = new Set(existing.map((p) => p.playlist)); 114 115 const newPlaylistItems = items 116 .filter((item) => !existingPlaylistNames.has(item.name ?? "Untitled")) 117 .flatMap((item) => { 118 const playlistName = item.name ?? "Untitled"; 119 const isUnordered = !!item.collection; 120 121 /** @type {PlaylistItem[]} */ 122 const playlistItems = []; 123 124 /** @type {any[]} */ (item.tracks ?? []).forEach((track, index) => { 125 playlistItems.push({ 126 $type: "sh.diffuse.output.playlistItem", 127 id: TID.now(), 128 playlist: playlistName, 129 positionedAfter: isUnordered 130 ? undefined 131 : index > 0 132 ? playlistItems[index - 1].id 133 : undefined, 134 criteria: [ 135 { 136 field: "tags.album", 137 value: track.album ?? "", 138 transformations: ["toLowerCase"], 139 }, 140 { 141 field: "tags.artist", 142 value: track.artist ?? "", 143 transformations: ["toLowerCase"], 144 }, 145 { 146 field: "tags.title", 147 value: track.title ?? "", 148 transformations: ["toLowerCase"], 149 }, 150 ], 151 createdAt: now, 152 updatedAt: now, 153 }); 154 }); 155 156 return playlistItems; 157 }); 158 159 await output.playlistItems.save([...existing, ...newPlaylistItems]); 160 const playlistCount = new Set(newPlaylistItems.map((p) => p.playlist)).size; 161 showStatus(`Imported ${playlistCount} playlist(s).`, "success"); 162 } catch (err) { 163 console.error("Import failed:", err); 164 showStatus(`Import failed: ${/** @type {Error} */ (err).message}`, "error"); 165 } 166};