feat: use broadcast channel to have applets talk to each other instead of providing context #3

closed
opened by tokono.ma targeting main from broadcast
Changed files
+50 -59
src
pages
configurator
engine
input
orchestrator
input-cache
single-queue
output
indexed-db
native-fs
processor
metadata-fetcher
scripts
applets
themes
webamp
+2 -4
src/pages/configurator/input/_applet.astro
··· 30 30 </style> 31 31 32 32 <script> 33 - import { applets } from "@web-applets/sdk"; 34 - 35 33 import type { Track } from "@applets/core/types.d.ts"; 36 - import { applet } from "@scripts/theme"; 34 + import { applet, register } from "@scripts/applets/common"; 37 35 38 36 //////////////////////////////////////////// 39 37 // SETUP 40 38 //////////////////////////////////////////// 41 - const context = applets.register<{ ready: boolean }>(); 39 + const context = register<{ ready: boolean }>(); 42 40 43 41 // Initial state 44 42 context.data = {
+2 -3
src/pages/configurator/output/_applet.astro
··· 39 39 import scope from "astro:scope"; 40 40 import { type Signal, computed, effect, signal } from "spellcaster/spellcaster.js"; 41 41 import { type ElementConfigurator, repeat, text } from "spellcaster/hyperscript.js"; 42 - import { applets } from "@web-applets/sdk"; 43 42 44 - import { applet, hs } from "@src/scripts/theme"; 43 + import { applet, hs, register } from "@scripts/applets/common"; 45 44 import type { OutputGetter, OutputSetter } from "@applets/core/types.d.ts"; 46 45 47 46 const METHODS = ["browser", "custom", "device"] as const; ··· 63 62 //////////////////////////////////////////// 64 63 // SETUP 65 64 //////////////////////////////////////////// 66 - const context = applets.register<{ ready: boolean }>(); 65 + const context = register<{ ready: boolean }>(); 67 66 68 67 // Applets container 69 68 const container = document.createElement("div");
+12 -13
src/pages/engine/audio/_applet.astro
··· 1 1 <script> 2 - import { applets } from "@web-applets/sdk"; 3 - import { State, Track, TrackState } from "./types"; 2 + import type { State, Track, TrackState } from "./types"; 3 + import { register } from "@scripts/applets/common"; 4 4 5 5 //////////////////////////////////////////// 6 6 // CONSTANTS ··· 11 11 //////////////////////////////////////////// 12 12 // SETUP 13 13 //////////////////////////////////////////// 14 - const context = applets.register<State>(); 15 - const container = document.createElement("div"); 14 + const context = register<State>(); 16 15 16 + // Audio elements container 17 + const container = document.createElement("div"); 17 18 container.id = "container"; 18 19 document.body.appendChild(container); 19 20 ··· 40 41 //////////////////////////////////////////// 41 42 // ACTIONS 42 43 //////////////////////////////////////////// 43 - context.setActionHandler( 44 - "render", 45 - async (args: { play?: { trackId: string; volume?: number }; tracks: Track[] }) => { 46 - await render(args.tracks); 47 - if (args.play) play({ trackId: args.play.trackId, volume: args.play.volume }); 48 - }, 49 - ); 50 - 51 44 context.setActionHandler("pause", pause); 52 45 context.setActionHandler("play", play); 53 46 context.setActionHandler("reload", reload); 47 + context.setActionHandler("render", render); 54 48 context.setActionHandler("seek", seek); 55 49 context.setActionHandler("volume", volume); 56 50 ··· 102 96 }); 103 97 } 104 98 99 + async function render(args: { play?: { trackId: string; volume?: number }; tracks: Track[] }) { 100 + await renderTracks(args.tracks); 101 + if (args.play) play({ trackId: args.play.trackId, volume: args.play.volume }); 102 + } 103 + 105 104 function seek({ percentage, trackId }: { percentage: number; trackId: string }) { 106 105 withAudioNode(trackId, (audio) => { 107 106 if (!isNaN(audio.duration)) { ··· 122 121 //////////////////////////////////////////// 123 122 // RENDER 124 123 //////////////////////////////////////////// 125 - async function render(tracks: Array<Track>) { 124 + async function renderTracks(tracks: Array<Track>) { 126 125 const ids = tracks.map((e) => e.id); 127 126 const existingNodes: Record<string, HTMLAudioElement> = {}; 128 127
+2 -2
src/pages/engine/queue/_applet.astro
··· 1 1 <script> 2 - import { applets } from "@web-applets/sdk"; 3 2 import { QueueItem, State } from "./types"; 3 + import { register } from "@scripts/applets/common"; 4 4 5 5 //////////////////////////////////////////// 6 6 // SETUP 7 7 //////////////////////////////////////////// 8 - const context = applets.register<State>(); 8 + const context = register<State>(); 9 9 10 10 // Initial state 11 11 context.data = {
+2 -2
src/pages/input/native-fs/_applet.astro
··· 16 16 </main> 17 17 18 18 <script> 19 - import { applets } from "@web-applets/sdk"; 20 19 import { computed, effect, Signal, signal } from "spellcaster"; 21 20 import { repeat, tags, text } from "spellcaster/hyperscript.js"; 22 21 import { type FileSystemDirectoryHandle, showDirectoryPicker } from "native-file-system-adapter"; ··· 26 25 27 26 import type { Track } from "@applets/core/types.d.ts"; 28 27 import { isAudioFile } from "@scripts/inputs/common"; 28 + import { register } from "@scripts/applets/common"; 29 29 30 30 import manifest from "./_manifest.json"; 31 31 ··· 41 41 const SCHEME = manifest.input_properties.scheme; 42 42 43 43 // Register applet 44 - const context = applets.register(); 44 + const context = register(); 45 45 46 46 //////////////////////////////////////////// 47 47 // UI
+3 -4
src/pages/input/s3/_applet.astro
··· 38 38 39 39 <script> 40 40 import { S3Client } from "@bradenmacdonald/s3-lite-client"; 41 - import { applets } from "@web-applets/sdk"; 42 41 import { computed, effect, Signal, signal } from "spellcaster"; 43 42 import { type Props, repeat, tags, text } from "spellcaster/hyperscript.js"; 44 43 import * as IDB from "idb-keyval"; ··· 46 45 import QS from "query-string"; 47 46 48 47 import type { Track } from "@applets/core/types.d.ts"; 49 - 50 - import manifest from "./_manifest.json"; 51 48 import { isAudioFile } from "@scripts/inputs/common"; 49 + import { register } from "@scripts/applets/common"; 50 + import manifest from "./_manifest.json"; 52 51 53 52 type Bucket = { 54 53 accessKey: string; ··· 86 85 const SCHEME = manifest.input_properties.scheme; 87 86 88 87 // Register applet 89 - const context = applets.register(); 88 + const context = register(); 90 89 91 90 //////////////////////////////////////////// 92 91 // UI
+6 -16
src/pages/orchestrator/single-queue/_applet.astro
··· 1 1 <script> 2 - import { applets } from "@web-applets/sdk"; 3 - 4 2 import type { ResolvedUri, Track } from "@applets/core/types.d.ts"; 5 - import { applet, comparable, reactive } from "@scripts/theme"; 3 + import { applet, comparable, reactive, register } from "@scripts/applets/common"; 6 4 7 5 //////////////////////////////////////////// 8 6 // SETUP ··· 12 10 import type * as OutputOrchestrator from "@applets/orchestrator/output-management/types.d.ts"; 13 11 14 12 // Register applet 15 - const context = applets.register<unknown>(); 13 + const context = register<unknown>(); 16 14 17 15 // Applet connections 18 16 const configurator = { 19 - input: await applet("../../configurator/input", { 20 - context: self.top || self.parent, 21 - }), 17 + input: await applet("../../configurator/input"), 22 18 }; 23 19 24 20 const engine = { 25 - audio: await applet<AudioEngine.State>("../../engine/audio", { 26 - context: self.top || self.parent, 27 - }), 28 - queue: await applet<QueueEngine.State>("../../engine/queue", { 29 - context: self.top || self.parent, 30 - }), 21 + audio: await applet<AudioEngine.State>("../../engine/audio"), 22 + queue: await applet<QueueEngine.State>("../../engine/queue"), 31 23 }; 32 24 33 25 const orchestrator = { 34 - output: await applet<OutputOrchestrator.State>("../../orchestrator/output-management", { 35 - context: self.top || self.parent, 36 - }), 26 + output: await applet<OutputOrchestrator.State>("../../orchestrator/output-management"), 37 27 }; 38 28 39 29 ////////////////////////////////////////////
+2 -2
src/pages/output/indexed-db/_applet.astro
··· 1 1 <script> 2 2 import * as IDB from "idb-keyval"; 3 - import { applets } from "@web-applets/sdk"; 4 3 5 4 import type { OutputGetter, OutputSetter } from "@applets/core/types.d.ts"; 5 + import { register } from "@scripts/applets/common"; 6 6 7 7 //////////////////////////////////////////// 8 8 // SETUP 9 9 //////////////////////////////////////////// 10 10 const IDB_PREFIX = "@applets/output/indexed-db"; 11 - const context = applets.register(); 11 + const context = register(); 12 12 13 13 //////////////////////////////////////////// 14 14 // ACTIONS
+2 -2
src/pages/output/native-fs/_applet.astro
··· 1 1 <script> 2 2 import * as IDB from "idb-keyval"; 3 - import { applets } from "@web-applets/sdk"; 4 3 import { type FileSystemDirectoryHandle, showDirectoryPicker } from "native-file-system-adapter"; 5 4 6 5 import type { OutputGetter, OutputSetter } from "@applets/core/types.d.ts"; 6 + import { register } from "@scripts/applets/common"; 7 7 8 8 //////////////////////////////////////////// 9 9 // SETUP ··· 11 11 const IDB_PREFIX = "@applets/output/native-fs"; 12 12 const IDB_DEVICE_KEY = `${IDB_PREFIX}/device`; 13 13 14 - const context = applets.register(); 14 + const context = register(); 15 15 16 16 //////////////////////////////////////////// 17 17 // ACTIONS
+4 -1
src/pages/processor/metadata-fetcher/_applet.astro
··· 24 24 async function extract(args: { mimeType?: string; stream?: ReadableStream; urls?: Urls }) { 25 25 // Construct records 26 26 // TODO: Use other metadata lib as fallback: https://github.com/buzz/mediainfo.js 27 - const { stats, tags } = await musicMetadataTags(args, false); 27 + const { stats, tags } = await musicMetadataTags(args, false).catch(() => ({ 28 + stats: undefined, 29 + tags: undefined, 30 + })); 28 31 29 32 // Fin 30 33 return { stats, tags };
+1 -1
src/scripts/themes/webamp/index.ts
··· 2 2 import { URLTrack } from "webamp"; 3 3 4 4 import type { ResolvedUri, Track } from "@applets/core/types.d.ts"; 5 - import { applet } from "../../theme.ts"; 5 + import { applet } from "@scripts/applets/common"; 6 6 7 7 //////////////////////////////////////////// 8 8 // 🎨 Styles
+3
astro.config.js
··· 11 11 }, 12 12 vite: { 13 13 plugins: [wasm()], 14 + server: { 15 + hmr: false, 16 + }, 14 17 }, 15 18 });
+3 -3
src/pages/index.astro
··· 141 141 </Applet> 142 142 143 143 <Applet title="Orchestrators" list={orchestrators}> 144 - These too are applet compositions. However, unlike themes, these are purely logical, and 145 - reuse applet instances from the parent context (when available). Mostly exist in order to 146 - construct sensible defaults to use across themes and abstractions. 144 + These too are applet compositions. However, unlike themes, these are purely logical. 145 + Mostly exist in order to construct sensible defaults to use across themes and 146 + abstractions. 147 147 </Applet> 148 148 149 149 <Applet title="Output" list={output}>
-6
src/pages/orchestrator/input-cache/_applet.astro
··· 49 49 50 50 await waitUntilAppletIsReady(configurator.input); 51 51 52 - console.log("Ready."); 53 - 54 52 const cachedTracks = orchestrator.output.data.tracks.collection; 55 53 await configurator.input.sendAction("contextualize", cachedTracks); 56 54 57 - console.log("Start list."); 58 - 59 55 const tracks = await configurator.input.sendAction<Track[]>("list", cachedTracks, { 60 56 timeoutDuration: 60000 * 60 * 24, 61 57 }); 62 58 63 - console.log("Got tracks", tracks); 64 - 65 59 // Process 66 60 const tracksWithMetadata = await tracks.reduce( 67 61 async (promise: Promise<Track[]>, track: Track) => {
+6
src/scripts/applets/common.ts
··· 1 1 import type { Applet, AppletEvent } from "@web-applets/sdk"; 2 2 3 + import QS from "query-string"; 3 4 import { applets } from "@web-applets/sdk"; 4 5 import { type ElementConfigurator, h } from "spellcaster/hyperscript.js"; 5 6 import { effect, isSignal, Signal, signal } from "spellcaster/spellcaster.js"; ··· 12 13 src: string, 13 14 opts: { 14 15 addSlashSuffix?: boolean; 16 + applets?: Record<string, string>; 15 17 container?: HTMLElement | Element; 16 18 id?: string; 17 19 setHeight?: boolean; ··· 25 27 : "" 26 28 }`; 27 29 30 + if (opts.applets) { 31 + src = QS.stringifyUrl({ url: src, query: opts.applets }); 32 + } 33 + 28 34 const existingFrame: HTMLIFrameElement | null = window.document.querySelector(`[src="${src}"]`); 29 35 30 36 let frame;