A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: basic search processor integration for webamp browser

+114 -27
+1 -1
deno.jsonc
··· 12 12 "@kunkun/kkrpc": "jsr:@kunkun/kkrpc@^0.6.0", 13 13 "@mary/ds-queue": "jsr:@mary/ds-queue@^0.1.3", 14 14 "@okikio/transferables": "jsr:@okikio/transferables@^1.0.2", 15 - "@orama/orama": "jsr:@orama/orama@^2.0.6", 15 + "@orama/orama": "npm:@orama/orama@^3.1.18", 16 16 "@phosphor-icons/web": "npm:@phosphor-icons/web@^2.1.2", 17 17 "@vicary/debounce-microtask": "jsr:@vicary/debounce-microtask@^0.1.8", 18 18 "alien-signals": "npm:alien-signals@^3.0.0",
+33 -5
src/components/processor/search/element.js
··· 1 1 import { DiffuseElement } from "@common/element.js"; 2 + import { signal } from "@common/signal.js"; 3 + import { listen } from "@common/worker.js"; 2 4 3 5 /** 4 6 * @import {ProxiedActions} from "@common/worker.d.ts"; 5 - * @import {Actions} from "./types.d.ts" 7 + * @import {Actions, State} from "./types.d.ts" 6 8 */ 7 9 8 10 //////////////////////////////////////////// ··· 19 21 constructor() { 20 22 super(); 21 23 22 - /** @type {ProxiedActions<Actions>} */ 23 - const p = this.workerProxy(); 24 + /** @type {ProxiedActions<Actions & State>} */ 25 + this.proxy = this.workerProxy(); 26 + 27 + this.search = this.proxy.search; 28 + this.supply = this.proxy.supply; 29 + } 30 + 31 + // SIGNALS 32 + 33 + #cacheId = signal(/** @type {string} */ ("")); 24 34 25 - this.search = p.search; 26 - this.supply = p.supply; 35 + // STATE 36 + 37 + cacheId = this.#cacheId.get; 38 + 39 + // LIFECYCLE 40 + 41 + /** 42 + * @override 43 + */ 44 + connectedCallback() { 45 + super.connectedCallback(); 46 + 47 + // Sync data with worker 48 + const link = this.workerLink(); 49 + 50 + // Listen for remote data changes 51 + listen("cacheId", this.#cacheId.set, link); 52 + 53 + // Fetch current data state 54 + this.proxy.cacheId().then(this.#cacheId.set); 27 55 } 28 56 } 29 57
+5
src/components/processor/search/types.d.ts
··· 1 + import type { SignalReader } from "@common/signal.d.ts"; 1 2 import type { Track } from "@definitions/types.d.ts"; 2 3 3 4 export type Actions = { 4 5 search(term: string): Promise<Track[]>; 5 6 supply(tracks: Track[]): Promise<void>; 6 7 }; 8 + 9 + export type State = { 10 + cacheId: SignalReader<string>; 11 + };
+20 -12
src/components/processor/search/worker.js
··· 3 3 // import { pluginQPS } from "@orama/plugin-qps"; 4 4 5 5 import { SCHEMA } from "./constants.js"; 6 - import { ostiary, rpc } from "@common/worker.js"; 7 - import { signal } from "@common/signal.js"; 6 + import { announce, ostiary, rpc } from "@common/worker.js"; 7 + import { effect, signal } from "@common/signal.js"; 8 8 9 9 /** 10 10 * @import {Track} from "@definitions/types.d.ts" ··· 15 15 // STATE 16 16 //////////////////////////////////////////// 17 17 18 - export const cacheId = signal(/** @type {string} */ ("")); 19 - export const inserted = signal(/** @type {Set<string>} */ (new Set())); 18 + export const $inserted = signal(/** @type {Set<string>} */ (new Set())); 19 + 20 + // Communicated state 21 + export const $cacheId = signal(/** @type {string} */ ("")); 20 22 21 23 //////////////////////////////////////////// 22 24 // DATABASE ··· 71 73 tracksMap[track.id] = track; 72 74 }); 73 75 74 - const currentSet = inserted.value; 76 + const currentSet = $inserted.value; 75 77 const newSet = new Set(ids); 76 78 77 79 const removedIds = currentSet.difference(newSet); 78 80 const newIds = newSet.difference(currentSet); 79 81 const newTracks = Array.from(newIds).map((id) => tracksMap[id]); 80 82 81 - await Orama.removeMultiple(await db, Array.from(removedIds)); 82 - await Orama.insertMultiple(await db, newTracks); 83 + await Orama.removeMultiple(db, Array.from(removedIds)); 84 + await Orama.insertMultiple(db, newTracks); 83 85 84 - inserted.value = newSet; 85 - cacheId.value = xxh32(ids.sort().join("")).toString(); 86 + $inserted.value = newSet; 87 + $cacheId.value = xxh32(ids.sort().join("")).toString(); 86 88 } 87 89 88 90 //////////////////////////////////////////// ··· 93 95 rpc(context, { 94 96 search, 95 97 supply, 98 + 99 + // State 100 + cacheId: $cacheId.get, 96 101 }); 102 + 103 + // Effects 104 + 105 + // Communicate state 106 + effect(() => announce("cacheId", $cacheId.value, context)); 97 107 }); 98 108 99 109 //////////////////////////////////////////// ··· 105 115 * @param {Track[]} tracks 106 116 */ 107 117 async function _search(term, tracks) { 108 - console.log("Search with offset:", tracks.length); 109 - 110 - const results = await Orama.search(await db, { 118 + const results = await Orama.search(db, { 111 119 // mode: "hybrid", 112 120 term, 113 121 limit: 10000,
+42 -6
src/themes/webamp/browser/element.js
··· 1 1 import { DiffuseElement, query, whenElementsDefined } from "@common/element.js"; 2 + import { signal } from "@common/signal.js"; 2 3 3 4 /** 4 5 * @import {RenderArg} from "@common/element.d.ts" ··· 10 11 class Browser extends DiffuseElement { 11 12 constructor() { 12 13 super(); 14 + 13 15 this.attachShadow({ mode: "open" }); 16 + this.performSearch = this.performSearch.bind(this); 14 17 } 15 18 19 + // SIGNALS 20 + 21 + #searchResults = signal(/** @type {Track[]} */ ([])); 22 + 16 23 // LIFECYCLE 17 24 18 25 /** ··· 22 29 super.connectedCallback(); 23 30 24 31 /** @type {InputElement} */ 25 - this.input = query(this, "input-selector"); 32 + const input = query(this, "input-selector"); 26 33 27 34 /** @type {OutputElement<Track[]>} */ 28 - this.output = query(this, "output-selector"); 35 + const output = query(this, "output-selector"); 29 36 30 37 /** @type {import("@components/engine/queue/element.js").CLASS} */ 31 - this.queue = query(this, "queue-engine-selector"); 38 + const queue = query(this, "queue-engine-selector"); 39 + 40 + /** @type {import("@components/processor/search/element.js").CLASS} */ 41 + const search = query(this, "search-processor-selector"); 42 + 43 + this.input = input; 44 + this.output = output; 45 + this.queue = queue; 46 + this.search = search; 32 47 33 48 // Wait for the above dependencies to be defined, then render again. 34 - whenElementsDefined({ input: this.input, output: this.output }).then(() => { 49 + whenElementsDefined({ input, output, search }).then(() => { 50 + this.effect(() => { 51 + const _cacheId = search.cacheId(); 52 + this.performSearch(); 53 + }); 54 + 35 55 this.effect(() => { 36 56 this.forceRender(); 37 57 }); 38 58 }); 59 + 60 + // Effects 61 + this.effect(() => { 62 + const _results = this.#searchResults.value; 63 + this.root().querySelector(".sunken-panel")?.scrollTo(0, 0); 64 + }); 39 65 } 40 66 41 67 // EVENTS ··· 54 80 tr.parentElement?.querySelector("tr.highlighted")?.classList.remove( 55 81 "highlighted", 56 82 ); 83 + 57 84 tr.classList.add("highlighted"); 58 85 } 59 86 ··· 67 94 }); 68 95 } 69 96 97 + async performSearch() { 98 + /** @type {HTMLInputElement | null} */ 99 + const input = this.root().querySelector("#search-input"); 100 + const term = input?.value?.trim(); 101 + 102 + this.#searchResults.value = await this.search?.search(term ?? "") ?? []; 103 + } 104 + 70 105 // RENDER 71 106 72 107 /** 73 108 * @param {RenderArg} _ 74 109 */ 75 110 render({ html }) { 76 - const tracks = this.output?.tracks?.collection() ?? []; 111 + const tracks = this.#searchResults.value; 77 112 78 113 return html` 79 114 <link rel="stylesheet" href="../../styles/vendor/98.css" /> ··· 129 164 130 165 <search class="field-row"> 131 166 <label for="search-input">Search</label> 132 - <input id="search-input" type="search" /> 167 + <input id="search-input" type="search" @change="${this 168 + .performSearch}" /> 133 169 </search> 134 170 135 171 <div class="sunken-panel" style="width: 480px">
+3 -1
src/themes/webamp/index.js
··· 2 2 import "@components/input/s3/element.js"; 3 3 import "@components/orchestrator/input/element.js"; 4 4 import "@components/orchestrator/output/element.js"; 5 - import "@components/orchestrator/process-tracks/element.js"; 5 + // import "@components/orchestrator/process-tracks/element.js"; 6 6 import "@components/orchestrator/queue-tracks/element.js"; 7 + import "@components/orchestrator/search-tracks/element.js"; 7 8 import "@components/processor/metadata/element.js"; 9 + import "@components/processor/search/element.js"; 8 10 9 11 import * as Input from "@components/configurator/input/element.js"; 10 12 import * as Queue from "@components/engine/queue/element.js";
+7
src/themes/webamp/index.vto
··· 63 63 64 64 <!-- Processors --> 65 65 <dp-metadata></dp-metadata> 66 + <dp-search></dp-search> 66 67 67 68 <!-- Orchestrators --> 68 69 <do-input id="input"></do-input> ··· 79 80 output-selector="#output" 80 81 queue-engine-selector="de-queue" 81 82 ></do-queue-tracks> 83 + 84 + <do-search-tracks 85 + input-selector="#input" 86 + output-selector="#output" 87 + search-processor-selector="dp-search" 88 + ></do-search-tracks> 82 89 83 90 <!-- 84 91
+3 -2
src/themes/webamp/window-manager/element.js
··· 137 137 // ACTIONS 138 138 139 139 /** 140 - * @param {string} id 140 + * @param {string | null} activeId 141 141 */ 142 - activateWindow(id) { 142 + activateWindow(activeId) { 143 143 this.querySelectorAll("dtw-window").forEach(w => { 144 144 if (w instanceof WindowElement === false) return 145 145 ··· 264 264 input-selector="#input" 265 265 output-selector="#output" 266 266 queue-engine-selector="de-queue" 267 + search-processor-selector="dp-search" 267 268 ></dtw-browser> 268 269 </dtw-window> 269 270 `;