import { createEffect, createResource, createSignal, For, Show, Signal, type Accessor, type Setter } from "solid-js"; import "./PlaylistManager.css"; import "./Dialog.css"; import "toastify-js/src/toastify.css"; import type { AppOptions, Playlist, PlaylistItem } from "../types"; import { formatFilename, formatTime, generateUUID, toast } from "../utils"; import { ClearSVG, DeleteSVG, DownloadSVG, UploadSVG } from "./SVG"; import { deletePlaylist, loadPlaylists, savePlaylist } from "../offline"; import { LoadingScreen } from "./LoadingScreen"; interface PlaylistManagerProps { showPlaylistManager: Accessor; setShowPlaylistManager: Setter; playlist: Accessor; setPlaylist: Setter; playTrack: (track: PlaylistItem) => void; options: AppOptions; } export default function PlaylistManager(props: PlaylistManagerProps) { const [playlists, fetchPlaylists] = createResource( async () => { return await loadPlaylists(); }, { initialValue: [] }, ); const [loadingActions, setLoadingActions] = createSignal(0); let dialogElement!: HTMLDialogElement; createEffect(() => { if (props.showPlaylistManager()) { dialogElement.showModal(); fetchPlaylists.refetch(); } }); function handleClose() { dialogElement.close(); props.setShowPlaylistManager(false); } function getTotalDuration(playlist: Playlist) { if (playlist.items.length === 0) return 0; //for some reason "duration" is a string at runtime... return playlist.items .map((item) => Number.parseFloat(item.metadata.duration as unknown as string)) .reduce((a, b) => a + b); } function exportPlaylists() { toast("Exporting playlists...", "info"); const blob = new Blob([JSON.stringify(playlists())], { type: "application/json" }); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = "pico-playlists.json"; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); } function importPlaylists() { const input = document.createElement("input"); input.type = "file"; input.onchange = async (event) => { const target = event.target as HTMLInputElement; const file = target.files?.[0]; if (file) { try { toast("Importing playlists...", "info"); setLoadingActions((prev) => prev + 1); const decoder = new TextDecoder(); const imports = JSON.parse(decoder.decode(await file.bytes())) as Playlist[]; for (const imported of imports) { await savePlaylist(imported, true); } setLoadingActions((prev) => prev - 1); fetchPlaylists.refetch(); } catch (e) { toast("Importing playlists failed", "error", e); } } }; input.click(); } function generateIds(items: Omit[]): PlaylistItem[] { return items.map((item) => ({ ...item, id: generateUUID() })); } return ( e.target.nodeName === "DIALOG" && handleClose()}>
Playlists

0}> {(item) => { const [showSongs, setShowSongs] = createSignal(false); return (
{item.name}
{item.items.length} Items ({formatTime(getTotalDuration(item))})
{(item) => { return ( <> {formatFilename(item, props.options.playlistDisplay)}
); }}
); }}
); }