Experiment to rebuild Diffuse using web applets.
0

Configure Feed

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

1import type { Applet, AppletEvent } from "@web-applets/sdk"; 2 3import { applets } from "@web-applets/sdk"; 4import { type ElementConfigurator, h } from "spellcaster/hyperscript.js"; 5import { effect, isSignal, Signal, signal } from "spellcaster/spellcaster.js"; 6import { xxh32 } from "xxh32"; 7 8//////////////////////////////////////////// 9// 🪟 Applet initialiser 10//////////////////////////////////////////// 11export async function applet<D>( 12 src: string, 13 opts: { 14 addSlashSuffix?: boolean; 15 context?: Window; 16 container?: HTMLElement | Element; 17 id?: string; 18 setHeight?: boolean; 19 } = {}, 20): Promise<Applet<D>> { 21 src = `${src}${ 22 src.endsWith("/") 23 ? "" 24 : opts.addSlashSuffix === undefined || opts.addSlashSuffix === true 25 ? "/" 26 : "" 27 }`; 28 29 const existingFrame: HTMLIFrameElement | null = (opts.context || window).document.querySelector( 30 `[src="${src}"]`, 31 ); 32 33 let frame; 34 35 if (existingFrame) { 36 frame = existingFrame; 37 } else { 38 frame = document.createElement("iframe"); 39 frame.src = src; 40 if (opts.id) frame.id = opts.id; 41 42 if (opts.container) { 43 opts.container.appendChild(frame); 44 } else { 45 (opts.context || window).document.body.appendChild(frame); 46 } 47 } 48 49 if (frame.contentWindow === null) { 50 throw new Error("iframe does not have a contentWindow"); 51 } 52 53 const applet = await applets.connect<D>(frame.contentWindow, { 54 context: opts.context, 55 }); 56 57 if (opts.setHeight) { 58 applet.onresize = () => { 59 frame.height = `${applet.height}px`; 60 frame.classList.add("has-loaded"); 61 }; 62 } else { 63 if (frame.contentDocument?.readyState === "complete") { 64 frame.classList.add("has-loaded"); 65 } 66 67 frame.addEventListener("load", () => { 68 frame.classList.add("has-loaded"); 69 }); 70 } 71 72 return applet; 73} 74 75//////////////////////////////////////////// 76// 🔮 Reactive state management 77//////////////////////////////////////////// 78export function reactive<D, T>( 79 applet: Applet<D>, 80 dataFn: (data: D) => T, 81 effectFn: (t: T) => void, 82) { 83 const [getter, setter] = signal(dataFn(applet.data)); 84 85 effect(() => { 86 effectFn(getter()); 87 return undefined; 88 }); 89 90 applet.addEventListener("data", (event: AppletEvent) => { 91 setter(dataFn(event.data)); 92 }); 93} 94 95//////////////////////////////////////////// 96// 🛠️ 97//////////////////////////////////////////// 98export function addScope<O extends object>(astroScope: string, object: O): O { 99 return { 100 ...object, 101 attrs: { 102 ...((object as any).attrs || {}), 103 [`data-astro-cid-${astroScope}`]: "", 104 }, 105 }; 106} 107 108export function comparable(value: unknown) { 109 return xxh32(JSON.stringify(value)); 110} 111 112export function hs( 113 tag: string, 114 astroScope: string, 115 props?: Record<string, unknown> | Signal<Record<string, unknown>>, 116 configure?: ElementConfigurator, 117) { 118 const propsWithScope = 119 props && isSignal(props) 120 ? () => addScope(astroScope, props()) 121 : addScope(astroScope, props || {}); 122 123 return h(tag, propsWithScope, configure); 124} 125 126export function isPrimitive(test: unknown) { 127 return test !== Object(test); 128} 129 130export function waitUntilAppletIsReady(applet: Applet): Promise<void> { 131 return new Promise((resolve) => { 132 if (applet.data?.ready === true) { 133 resolve(); 134 return; 135 } 136 137 const callback = (event: AppletEvent) => { 138 if (event.data?.ready === true) { 139 applet.removeEventListener("data", callback); 140 resolve(); 141 } 142 }; 143 144 applet.addEventListener("data", callback); 145 }); 146}