Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

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

refactor: Move utility functions out of theme

+102 -63
+23
deno.lock
··· 2722 2722 "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" 2723 2723 } 2724 2724 }, 2725 + "redirects": { 2726 + "https://esm.sh/gh/unternet-co/web-applets/sdk": "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/sdk", 2727 + "https://esm.sh/gh/unternet-co/web-applets/sdk/src/index.ts": "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/sdk/src/index.ts" 2728 + }, 2729 + "remote": { 2730 + "https://denopkg.com/unternet-co/web-applets@e3e9ca6/sdk/src/index.ts": "46abe7bc8b6b678e4ecaee4905c9073816c62d22c5cbc5d6f5af0cda5ab27932", 2731 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk.mjs": "70d2b9a7dfca16423d99246050c7abec35cf661fe39a544559b7a5dd226118a8", 2732 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/applets/applet-factory.nobundle.mjs": "61956718100039677766b50b1a7e506b6ce18e6222d4d22c53480b458a2f60d9", 2733 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/applets/applet-scope.nobundle.mjs": "219c4fd6a6ae2cc6741a91562f5d33f892f8c6c8d1709ce5077032844ac18c95", 2734 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/applets/applet.nobundle.mjs": "38bf32e3fedca7d37f042a347417659d6bac8fe8ed04021730a6b7b95a102eab", 2735 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/applets/errors.nobundle.mjs": "e1b6e635b6cfa5d2fe35517b6d2b82c667174aa541fdb21bce87ec26233d052b", 2736 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/applets/events.nobundle.mjs": "e1cacd49dda2242f31a298352fc6a30f993bd0c380c5b18bfbf0eacac510d661", 2737 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/constants.nobundle.mjs": "9a9aaeb0797585f3d662cc5e4f9e097e84cedf9d731d1ea8eaef379ac45dea14", 2738 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/debug.nobundle.mjs": "85fe667af343ef0692b2c77d041cf85fd5a00a106946c481c68d609380349b6d", 2739 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/elements/applet-frame.nobundle.mjs": "a96218721638a5c47b6393871f595e4074931c5ab1c28d5a77f1d3df579e5e1a", 2740 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/index.nobundle.mjs": "5fa4b5a068f064c6f04ad93a21480226242746c7f5339141d2855280962e9bd0", 2741 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/index.ts.mjs": "adcbf7cd6f62cd5eb1352f654e47b44d597c859132cb9b22196db28498172fee", 2742 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/index.ts.nobundle.mjs": "56dc8dd8586a38eb48dee0d7046ab9488b0a95b13324bcf35779efbe6d0cbd93", 2743 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/denonext/sdk/src/utils.nobundle.mjs": "e3ccd0d6e05ee075c23fcca025c2cd4100a1e94e73b7df049a5e1b4358849d59", 2744 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/sdk": "65f33669ecd21ed2e5d40f99f375b2e443740ee0db358a5876841a3c5b717cf7", 2745 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/sdk/src/index.ts": "83d82acfcb7be175545d516c26ed04a7a828b5b2612a78a556370ab0bfe160be", 2746 + "https://esm.sh/gh/unternet-co/web-applets@e3e9ca6/sdk/src/index.ts?bundle=false": "f83daf050b47537389e05b8fd38c3b6db1a32725876a56b21e91df901af8ac92" 2747 + }, 2725 2748 "workspace": { 2726 2749 "dependencies": [ 2727 2750 "npm:@picocss/pico@^2.0.6",
+61
src/scripts/theme.ts
··· 1 + import type { 2 + Applet, 3 + AppletEvent, 4 + } from "https://denopkg.com/unternet-co/web-applets@e3e9ca6/sdk/src/index.ts"; 5 + import { applets } from "@web-applets/sdk"; 6 + import { effect, signal } from "spellcaster/spellcaster.js"; 7 + 8 + //////////////////////////////////////////// 9 + // 🪟 Applet initialiser 10 + //////////////////////////////////////////// 11 + export async function applet<D>( 12 + src: string, 13 + opts: { setHeight?: boolean } = {}, 14 + ): Promise<Applet<D>> { 15 + const frame: HTMLIFrameElement | null = document.querySelector( 16 + `[src="${src}${src.endsWith("/") ? "" : "/"}"]`, 17 + ); 18 + 19 + if (frame === null) throw new Error("iframe element not found, src: " + src); 20 + if (frame.contentWindow === null) { 21 + throw new Error("iframe does not have a contentWindow"); 22 + } 23 + 24 + const applet = await applets.connect<D>(frame.contentWindow); 25 + 26 + if (opts.setHeight) { 27 + applet.onresize = () => { 28 + frame.height = `${applet.height}px`; 29 + frame.classList.add("has-loaded"); 30 + }; 31 + } else { 32 + if (frame.contentDocument?.readyState === "complete") { 33 + frame.classList.add("has-loaded"); 34 + } 35 + 36 + frame.addEventListener("load", () => { 37 + frame.classList.add("has-loaded"); 38 + }); 39 + } 40 + 41 + return applet; 42 + } 43 + 44 + //////////////////////////////////////////// 45 + // 🔮 Reactive state management 46 + //////////////////////////////////////////// 47 + export function reactive<D, T>( 48 + applet: Applet<D>, 49 + dataFn: (data: D) => T, 50 + effectFn: (t: T) => void, 51 + ) { 52 + const [getter, setter] = signal( 53 + dataFn(applet.data), 54 + ); 55 + 56 + effect(() => effectFn(getter())); 57 + 58 + applet.addEventListener("data", (event: AppletEvent) => { 59 + setter(dataFn(event.data)); 60 + }); 61 + }
+18 -63
src/scripts/themes/pilot/index.ts
··· 1 - import { type Applet, type AppletEvent, applets } from "@web-applets/sdk"; 2 - import { effect, signal } from "spellcaster/spellcaster.js"; 1 + import { applet, reactive } from "../../theme.ts"; 3 2 4 - import type * as AudioEngine from "../../../applets/engine/audio/types.ts"; 5 - import type * as AudioUI from "../../../applets/themes/pilot/ui/audio/types.ts"; 6 - 3 + //////////////////////////////////////////// 4 + // 🎨 Styles 5 + //////////////////////////////////////////// 7 6 import "../../../styles/themes/pilot/index.css"; 8 7 9 8 //////////////////////////////////////////// 10 9 // 🗂️ Applets 11 10 //////////////////////////////////////////// 11 + import type * as AudioEngine from "../../../applets/engine/audio/types.ts"; 12 + import type * as AudioUI from "../../../applets/themes/pilot/ui/audio/types.ts"; 13 + 12 14 const engine = { 13 15 audio: await applet<AudioEngine.State>("../../engine/audio"), 14 16 }; 15 17 16 18 const ui = { 17 - audio: await applet("ui/audio", { setHeight: true }), 19 + audio: await applet<AudioUI.State>("ui/audio", { setHeight: true }), 18 20 }; 19 21 20 22 //////////////////////////////////////////// 21 - // ⚡ Connect applets 23 + // ▒▒ [Connections ⚡] 24 + // ▒▒ Audio UI → Audio Engine 22 25 //////////////////////////////////////////// 23 26 reactive( 24 27 ui.audio, 28 + // TODO: Shouldn't need to pass in the type here 25 29 (data: AudioUI.State) => data.isPlaying, 26 - (isPlaying: boolean) => { 30 + (isPlaying) => { 27 31 if (isPlaying) { 28 32 // TODO: Replace with an actual queue system (shift queue) 29 33 engine.audio.sendAction("render", { ··· 47 51 reactive( 48 52 ui.audio, 49 53 (data: AudioUI.State) => data.seekPosition, 50 - (seekPosition: undefined | number) => { 54 + (seekPosition) => { 51 55 if (seekPosition) { 52 56 engine.audio.sendAction("seek", { 53 57 percentage: seekPosition, ··· 57 61 }, 58 62 ); 59 63 64 + //////////////////////////////////////////// 65 + // ▒▒ [Connections ⚡] 66 + // ▒▒ Audio Engine → Audio UI 67 + //////////////////////////////////////////// 60 68 reactive( 61 69 engine.audio, 62 70 (data: AudioEngine.State) => 63 71 // TODO: Simplify using queue engine 64 72 Object.values(data.nowPlaying).some((s) => s.isPlaying), 65 - (isPlaying: boolean) => ui.audio.sendAction("set_is_playing", isPlaying), 73 + (isPlaying) => ui.audio.sendAction("set_is_playing", isPlaying), 66 74 ); 67 75 68 76 reactive( ··· 75 83 }, 76 84 (progress: number) => ui.audio.sendAction("set_progress", progress), 77 85 ); 78 - 79 - //////////////////////////////////////////// 80 - // 🪟 Applet initialiser 81 - //////////////////////////////////////////// 82 - async function applet<T>(src: string, opts: { setHeight?: boolean } = {}) { 83 - const frame: HTMLIFrameElement | null = document.querySelector( 84 - `[src="${src}${src.endsWith("/") ? "" : "/"}"]`, 85 - ); 86 - 87 - if (frame === null) throw new Error("iframe element not found, src: " + src); 88 - if (frame.contentWindow === null) { 89 - throw new Error("iframe does not have a contentWindow"); 90 - } 91 - 92 - const applet: Applet = await applets.connect<T>(frame.contentWindow); 93 - 94 - if (opts.setHeight) { 95 - applet.onresize = () => { 96 - frame.height = `${applet.height}px`; 97 - frame.classList.add("has-loaded"); 98 - }; 99 - } else { 100 - if (frame.contentDocument?.readyState === "complete") { 101 - frame.classList.add("has-loaded"); 102 - } 103 - 104 - frame.addEventListener("load", () => { 105 - frame.classList.add("has-loaded"); 106 - }); 107 - } 108 - 109 - return applet; 110 - } 111 - 112 - //////////////////////////////////////////// 113 - // 🔮 Reactive state management 114 - //////////////////////////////////////////// 115 - // TODO: Applet should have a subtype 116 - function reactive<T>( 117 - applet: Applet, 118 - dataFn: (data: any /* TODO: Proper types pls */) => T, 119 - effectFn: (t: T) => void, 120 - ) { 121 - const [getter, setter] = signal( 122 - dataFn(applet.data as any), 123 - ); 124 - 125 - effect(() => effectFn(getter())); 126 - 127 - applet.addEventListener("data", (event: AppletEvent) => { 128 - setter(dataFn(event.data)); 129 - }); 130 - }