A music player that connects to your cloud/distributed storage.
1import * as IDB from "idb-keyval";
2
3import { computed, signal } from "~/common/signal.js";
4import { BroadcastedOutputElement, outputManager } from "../../common.js";
5import { defineElement } from "~/common/element.js";
6
7const STORAGE_PREFIX = "diffuse/output/bytes/s3";
8
9/**
10 * @import {ProxiedActions} from "~/common/worker.d.ts"
11 * @import {OutputElement, OutputManager} from "../../types.d.ts"
12 * @import {Bucket} from "~/components/input/s3/types.d.ts"
13 * @import {S3OutputElement, S3OutputWorkerActions} from "./types.d.ts"
14 */
15
16////////////////////////////////////////////
17// ELEMENT
18////////////////////////////////////////////
19
20/**
21 * @implements {OutputElement<Uint8Array | undefined>}
22 * @implements {S3OutputElement}
23 */
24class S3Output extends BroadcastedOutputElement {
25 static NAME = "diffuse/output/bytes/s3";
26 static WORKER_URL = "components/output/bytes/s3/worker.js";
27
28 #manager;
29
30 constructor() {
31 super();
32
33 /** @type {ProxiedActions<S3OutputWorkerActions>} */
34 this.proxy = this.workerProxy();
35
36 /** @type {OutputManager<Uint8Array | undefined>} */
37 this.#manager = outputManager({
38 facets: {
39 empty: () => undefined,
40 get: () => this.#get("facets"),
41 put: (data) => this.#put("facets", data),
42 },
43 init: () => this.whenConnected(),
44 playlistItems: {
45 empty: () => undefined,
46 get: () => this.#get("playlistItems"),
47 put: (data) => this.#put("playlistItems", data),
48 },
49 settings: {
50 empty: () => undefined,
51 get: () => this.#get("settings"),
52 put: (data) => this.#put("settings", data),
53 },
54 tracks: {
55 empty: () => undefined,
56 get: () => this.#get("tracks"),
57 put: (data) => this.#put("tracks", data),
58 },
59 });
60
61 this.facets = this.#manager.facets;
62 this.playlistItems = this.#manager.playlistItems;
63 this.settings = this.#manager.settings;
64 this.tracks = this.#manager.tracks;
65 }
66
67 // SIGNALS
68
69 #isOnline = signal(navigator.onLine);
70
71 // STATE
72
73 ready = computed(() => {
74 return this.#bucket.value !== undefined && this.#isOnline.value;
75 });
76
77 // LIFECYCLE
78
79 /**
80 * @override
81 */
82 async connectedCallback() {
83 this.replicateSavedData(this.#manager);
84
85 super.connectedCallback();
86
87 /** @type {Bucket | undefined} */
88 const stored = await IDB.get(`${STORAGE_PREFIX}/bucket`);
89 if (stored) this.#bucket.value = stored;
90
91 globalThis.addEventListener("online", this.#online);
92 globalThis.addEventListener("offline", this.#offline);
93 }
94
95 /** @override */
96 disconnectedCallback() {
97 globalThis.removeEventListener("online", this.#online);
98 globalThis.removeEventListener("offline", this.#offline);
99 }
100
101 #offline = () => this.#isOnline.set(false);
102 #online = () => this.#isOnline.set(true);
103
104 // BUCKET
105
106 #bucket = signal(/** @type {Bucket | undefined} */ (undefined));
107
108 bucket = this.#bucket.get;
109
110 /** @returns {Promise<Bucket | undefined>} */
111 async getBucket() {
112 if (!this.#bucket.value) {
113 /** @type {Bucket | undefined} */
114 const stored = await IDB.get(`${STORAGE_PREFIX}/bucket`);
115 if (stored) this.#bucket.value = stored;
116 return stored;
117 }
118
119 return this.#bucket.value;
120 }
121
122 /**
123 * @param {Bucket} bucket
124 */
125 async setBucket(bucket) {
126 this.#bucket.value = bucket;
127 await IDB.set(`${STORAGE_PREFIX}/bucket`, bucket);
128 }
129
130 async unsetBucket() {
131 this.#bucket.value = undefined;
132 await IDB.del(`${STORAGE_PREFIX}/bucket`);
133 }
134
135 // GET & PUT
136
137 /** @param {string} name */
138 #get = async (name) => {
139 const bucket = await this.getBucket();
140 if (!bucket) return undefined;
141 return this.proxy.get({ bucket, name: this.#cat(name) });
142 };
143
144 /** @param {string} name; @param {any} data */
145 #put = async (name, data) => {
146 const bucket = await this.getBucket();
147 if (!bucket) return undefined;
148 return this.proxy.put({ bucket, data, name: this.#cat(name) });
149 };
150
151 // 🛠️
152
153 /** @param {string} name */
154 #cat(name) {
155 const ns = this.namespace;
156 return `${ns?.length ? ns + "/" : ""}${name}`;
157 }
158}
159
160export default S3Output;
161
162////////////////////////////////////////////
163// REGISTER
164////////////////////////////////////////////
165
166export const CLASS = S3Output;
167export const NAME = "dob-s3";
168
169defineElement(NAME, S3Output);