forked from
tokono.ma/diffuse
A music player that connects to your cloud/distributed storage.
1import { computed } from "~/common/signal.js";
2import { OutputTransformer } from "~/components/transformer/output/base.js";
3import { defineElement } from "~/common/element.js";
4
5/**
6 * @import {PlaylistItem, Track} from "~/definitions/types.d.ts"
7 */
8
9////////////////////////////////////////////
10// ELEMENT
11////////////////////////////////////////////
12
13class PathCollectionsOrchestrator extends OutputTransformer {
14 static NAME = "diffuse/orchestrator/path-collections";
15
16 constructor() {
17 super();
18
19 const base = this.base();
20
21 const ephemeralItems = computed(() => {
22 const col = base.tracks.collection();
23 return createEphemeralItems(col.state === "loaded" ? col.data : []);
24 });
25
26 this.facets = base.facets;
27 this.settings = base.settings;
28 this.playlistItems = {
29 ...base.playlistItems,
30 collection: computed(() => {
31 const col = base.playlistItems.collection();
32 if (col.state !== "loaded") return col;
33 return { state: "loaded", data: [...col.data, ...ephemeralItems()] };
34 }),
35 /** @type {(typeof base.playlistItems.save)} */
36 save: async (items) => {
37 await base.playlistItems.save(
38 (items || []).filter((p) => !p.ephemeral),
39 );
40 },
41 };
42 this.tracks = base.tracks;
43
44 this.ready = base.ready;
45 }
46}
47
48/**
49 * @param {Track[]} tracks
50 * @returns {PlaylistItem[]}
51 */
52function createEphemeralItems(tracks) {
53 /** @type {PlaylistItem[]} */
54 const items = [];
55
56 /** @type {Set<string>} */
57 const segmentsAdded = new Set();
58
59 tracks.forEach((track) => {
60 const segment = pathSegment(track.uri);
61 if (!segment) return;
62
63 if (segmentsAdded.has(segment)) return;
64
65 /** @type {PlaylistItem} */
66 const item = {
67 $type: "sh.diffuse.output.playlistItem",
68 id: `path-collections/${track.id}`,
69 playlist: segment,
70 criteria: [{ field: "uri", value: /** @type {any} */ (track.uri) }],
71 ephemeral: true,
72 };
73
74 items.push(item);
75 segmentsAdded.add(segment);
76 });
77
78 return items;
79}
80
81/**
82 * @param {string} uri
83 * @returns {string | undefined}
84 */
85function pathSegment(uri) {
86 try {
87 const url = new URL(uri);
88 const segment = url.pathname.split("/").filter(Boolean)[0];
89 return segment || undefined;
90 } catch {
91 return undefined;
92 }
93}
94
95export default PathCollectionsOrchestrator;
96
97////////////////////////////////////////////
98// REGISTER
99////////////////////////////////////////////
100
101export const CLASS = PathCollectionsOrchestrator;
102export const NAME = "do-path-collections";
103
104defineElement(NAME, CLASS);