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`
+
+ `,
+ 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);