From d43ce1d3afe08f54fbf87fe7a0a6361bad88c3b2 Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Sun, 26 Apr 2026 23:47:12 +0200 Subject: [PATCH] chore: create playlist via playlist facet --- src/_data/facets.json | 2 +- src/facets/data/playlists/index.html | 11 +- src/facets/data/playlists/index.inline.js | 209 +++++++++++++++++----- src/styles/diffuse/facet.css | 24 +++ 4 files changed, 200 insertions(+), 46 deletions(-) diff --git a/src/_data/facets.json b/src/_data/facets.json index dd707416..90d6eb30 100644 --- a/src/_data/facets.json +++ b/src/_data/facets.json @@ -146,7 +146,7 @@ "title": "Playlists", "category": "Data", "featured": true, - "desc": "An overview of all playlists and their contents." + "desc": "Manage your playlists." }, { "url": "facets/playback/preload/prelude/index.html", diff --git a/src/facets/data/playlists/index.html b/src/facets/data/playlists/index.html index d236c087..8e750c47 100644 --- a/src/facets/data/playlists/index.html +++ b/src/facets/data/playlists/index.html @@ -1,7 +1,9 @@ +
@@ -114,6 +117,12 @@

Playlists

An overview of all playlists and their contents.

+

+ +

diff --git a/src/facets/data/playlists/index.inline.js b/src/facets/data/playlists/index.inline.js index 5cf8a897..d522412b 100644 --- a/src/facets/data/playlists/index.inline.js +++ b/src/facets/data/playlists/index.inline.js @@ -2,6 +2,7 @@ import { html, render as litRender } from "lit-html"; import * as Output from "~/common/output.js"; import * as Playlist from "~/common/playlist.js"; +import * as TID from "@atcute/tid"; import foundation from "~/common/foundation.js"; import { effect } from "~/common/signal.js"; @@ -28,12 +29,26 @@ const list = const empty = /** @type {HTMLElement} */ (document.querySelector("#playlists-empty")); const dialog = - /** @type {HTMLDialogElement} */ (document.querySelector("#playlists-dialog")); + /** @type {HTMLDialogElement} */ (document.querySelector( + "#playlists-dialog", + )); +const createDialog = + /** @type {HTMLDialogElement} */ (document.querySelector( + "#create-playlist-dialog", + )); + +document.querySelector("#create-playlist-btn")?.addEventListener( + "click", + () => { + showCreatePlaylist(); + }, +); effect(() => { const playlistItemsCol = outputOrchestrator.playlistItems.collection(); - const playlistItems = - playlistItemsCol.state === "loaded" ? playlistItemsCol.data : []; + const playlistItems = playlistItemsCol.state === "loaded" + ? playlistItemsCol.data + : []; const tracksCol = outputOrchestrator.tracks.collection(); const tracks = tracksCol.state === "loaded" ? tracksCol.data : []; @@ -48,10 +63,11 @@ effect(() => { empty.hidden = playlists.length > 0; /** @param {typeof playlists} group @param {number} offset */ - const renderGroup = (group, offset) => group.map(({ name, items }, index) => { - const menuId = `playlists-menu-${offset + index}`; - return html` -
  • + const renderGroup = (group, offset) => + group.map(({ name, items }, index) => { + const menuId = `playlists-menu-${offset + index}`; + return html` +
  • ${name}
    @@ -65,7 +81,8 @@ effect(() => {
  • `; - }); + }); litRender( html` - ${orderedPlaylists.length > 0 ? html` -
  • Ordered
  • - ${renderGroup(orderedPlaylists, 0)} - ` : null} - ${unorderedPlaylists.length > 0 ? html` -
  • Not ordered
  • - ${renderGroup(unorderedPlaylists, orderedPlaylists.length)} - ` : null} + ${orderedPlaylists.length > 0 + ? html` +
  • Ordered
  • + ${renderGroup(orderedPlaylists, 0)} + ` + : null} ${unorderedPlaylists.length > 0 + ? html` +
  • Not ordered
  • + ${renderGroup(unorderedPlaylists, orderedPlaylists.length)} + ` + : null} `, list, ); @@ -213,13 +235,23 @@ function showDetails(name, tracks, items) { } } - found.sort((a, b) => (a.tags?.title ?? "").localeCompare(b.tags?.title ?? "")); + found.sort((a, b) => + (a.tags?.title ?? "").localeCompare(b.tags?.title ?? "") + ); const notFound = notFoundItems .map((item) => ({ - title: String(item.criteria.find((c) => c.field === "tags.title")?.value ?? ""), - artist: String(item.criteria.find((c) => c.field === "tags.artist")?.value ?? "") || null, - album: String(item.criteria.find((c) => c.field === "tags.album")?.value ?? "") || null, + title: String( + item.criteria.find((c) => c.field === "tags.title")?.value ?? "", + ), + artist: + String( + item.criteria.find((c) => c.field === "tags.artist")?.value ?? "", + ) || null, + album: + String( + item.criteria.find((c) => c.field === "tags.album")?.value ?? "", + ) || null, })) .sort((a, b) => a.title.localeCompare(b.title)); @@ -238,29 +270,47 @@ function showDetails(name, tracks, items) {
    ${found.length} found
      - ${found.map((t) => html` -
    • - ${t.tags?.title ?? t.uri} - ${t.tags?.artist - ? html`${t.tags.artist}` - : null} -
    • - `)} -
    -
    - ${notFound.length > 0 ? html` -
    - ${notFound.length} not found -
      - ${notFound.map(({ title, artist, album }) => html` + ${found.map((t) => + html`
    • - ${title} - ${artist ? html`${artist}${album ? html` · ${album}` : null}` : null} + ${t.tags?.title ?? + t.uri} + ${t.tags?.artist + ? html` + ${t.tags.artist} + ` + : null}
    • - `)} -
    -
    - ` : null} + ` + )} + +
    + ${notFound.length > 0 + ? html` +
    + ${notFound + .length} not found +
      + ${notFound.map(({ title, artist, album }) => + html` +
    • + ${title} + ${artist + ? html` + ${artist}${album + ? html` + · ${album} + ` + : null} + ` + : null} +
    • + ` + )} +
    +
    + ` + : null} `, dialog, @@ -269,6 +319,77 @@ function showDetails(name, tracks, items) { dialog.showModal(); } +function showCreatePlaylist() { + litRender( + html` +
    + Create playlist + +
    +
    +
    + +

    + +

    +
    +
    + `, + createDialog, + ); + + createDialog.showModal(); +} + +/** @param {string} name */ +async function createPlaylist(name) { + const existing = await Output.data(outputOrchestrator.playlistItems); + + const now = new Date().toISOString(); + + /** @type {import("~/definitions/types.d.ts").PlaylistItem} */ + const item = { + $type: "sh.diffuse.output.playlistItem", + id: TID.now(), + playlist: name, + criteria: [], + createdAt: now, + updatedAt: now, + }; + + await outputOrchestrator.playlistItems.save([...existing, item]); +} + //////////////////////////////////////////// // 🚀 //////////////////////////////////////////// diff --git a/src/styles/diffuse/facet.css b/src/styles/diffuse/facet.css index 82f55a82..e29fa17f 100644 --- a/src/styles/diffuse/facet.css +++ b/src/styles/diffuse/facet.css @@ -286,6 +286,30 @@ p, margin: var(--space-sm) 0; } +.create-form { + display: flex; + flex-direction: column; + gap: var(--space-sm); +} + +.create-form__label { + font-size: var(--fs-xs); + font-weight: 600; + letter-spacing: var(--tracking-wider); + opacity: 0.5; + text-transform: uppercase; +} + +.create-form__checkbox-row { + align-items: center; + flex-direction: row; + gap: var(--space-2xs); + + input { + width: auto; + } +} + .dropdown { background: oklch(from var(--bg-color) calc(l + 0.2) c h / 0.975); border: 1px solid var(--border-color); -- 2.51.2