A music player that connects to your cloud/distributed storage.
at v4 169 lines 4.3 kB view raw
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);