A music player that connects to your cloud/distributed storage.
1import * as TID from "@atcute/tid";
2import { DiffuseElement } from "~/common/element.js";
3import { SCHEME } from "./constants.js";
4import {
5 buildURI,
6 loadHandles,
7 saveHandles,
8 tidsFromTracks,
9} from "./common.js";
10
11/**
12 * @import {InputActions, InputSchemeProvider} from "~/components/input/types.d.ts"
13 * @import {ProxiedActions} from "~/common/worker.d.ts"
14 * @import {Track} from "~/definitions/types.d.ts"
15 */
16
17////////////////////////////////////////////
18// ELEMENT
19////////////////////////////////////////////
20
21/**
22 * @implements {ProxiedActions<InputActions>}
23 * @implements {InputSchemeProvider}
24 */
25class LocalInput extends DiffuseElement {
26 static NAME = "diffuse/input/local";
27 static WORKER_URL = "components/input/local/worker.js";
28
29 SCHEME = SCHEME;
30
31 /** @type {Map<string, string>} tid → handle name */
32 #names = new Map();
33
34 constructor() {
35 super();
36
37 /** @type {ProxiedActions<InputActions>} */
38 this.proxy = this.workerProxy();
39
40 this.artwork = this.proxy.artwork;
41 this.consult = this.proxy.consult;
42 this.detach = this.proxy.detach;
43 this.groupConsult = this.proxy.groupConsult;
44 this.list = this.proxy.list;
45 this.resolve = this.proxy.resolve;
46 }
47
48 // LIFECYCLE
49
50 /** @override */
51 async connectedCallback() {
52 super.connectedCallback();
53 const handles = await loadHandles();
54 for (const [tid, handle] of Object.entries(handles)) {
55 this.#names.set(tid, handle.name);
56 }
57 }
58
59 // 🛠️
60
61 /**
62 * Prompts the user to pick a directory.
63 * Stores handle in IDB.
64 * Returns the URI for the track placeholder.
65 */
66 async addDirectory() {
67 const dirHandle = await /** @type {any} */ (globalThis).showDirectoryPicker(
68 { mode: "read" },
69 );
70
71 const tid = TID.now();
72 const handles = await loadHandles();
73
74 handles[tid] = dirHandle;
75 await saveHandles(handles);
76 this.#names.set(tid, dirHandle.name);
77
78 return buildURI(tid);
79 }
80
81 /**
82 * Prompts the user to pick one or more files.
83 * Stores handles in IDB.
84 * Returns the URIs for the track placeholders.
85 */
86 async addFiles() {
87 const fileHandles = await /** @type {any} */ (globalThis)
88 .showOpenFilePicker({ multiple: true });
89 const handles = await loadHandles();
90 const uris = [];
91
92 for (const fileHandle of fileHandles) {
93 const tid = TID.now();
94 handles[tid] = fileHandle;
95 this.#names.set(tid, fileHandle.name);
96 uris.push(buildURI(tid));
97 }
98
99 await saveHandles(handles);
100 return uris;
101 }
102
103 /** @param {Track[]} tracks */
104 sources(tracks) {
105 return Object.values(tidsFromTracks(tracks)).map((tid) => ({
106 label: this.#names.get(tid) ?? tid,
107 uri: buildURI(tid),
108 }));
109 }
110}
111
112export default LocalInput;
113
114////////////////////////////////////////////
115// REGISTER
116////////////////////////////////////////////
117
118export const CLASS = LocalInput;
119export const NAME = "di-local";
120
121customElements.define(NAME, CLASS);