Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

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

chore: Restructure

+329 -198
+53
src/applets/engine/audio/applet.astro
··· 1 + <div id="container"> 2 + <audio 3 + src="https://archive.org/download/lp_moonlight-sonata_ludwig-van-beethoven-frdric-chopin-alexand/disc1%2F01.02.%20Moonlight%20Sonata%20Op.%2027%2C%20No.%202%20In%20C%20Sharp%20Minor%3A%20Allegretto.mp3?tunnel=1" 4 + ></audio> 5 + </div> 6 + 7 + <script> 8 + import { applets } from "@web-applets/sdk"; 9 + 10 + interface State { 11 + isPlaying: boolean; 12 + progress: number; 13 + } 14 + 15 + const context = applets.register<State>(); 16 + const container = document.querySelector("#container"); 17 + const audio = document.querySelector("audio"); 18 + 19 + //////////////////////////////////////////// 20 + // Initial state 21 + //////////////////////////////////////////// 22 + context.data = { 23 + isPlaying: false, 24 + progress: 0, 25 + }; 26 + 27 + //////////////////////////////////////////// 28 + // Audio events 29 + //////////////////////////////////////////// 30 + audio.ontimeupdate = (event) => { 31 + const progress = 32 + isNaN(audio.duration) || audio.duration === 0 ? 0 : audio.currentTime / audio.duration; 33 + context.data = { ...context.data, progress }; 34 + }; 35 + 36 + audio.onpause = () => (context.data = { ...context.data, isPlaying: false }); 37 + audio.onplay = () => (context.data = { ...context.data, isPlaying: true }); 38 + 39 + //////////////////////////////////////////// 40 + // Actions 41 + //////////////////////////////////////////// 42 + context.setActionHandler("load", (src: string) => { 43 + audio.src = src; 44 + }); 45 + 46 + context.setActionHandler("play", () => { 47 + audio.play(); 48 + }); 49 + 50 + context.setActionHandler("pause", () => { 51 + audio.pause(); 52 + }); 53 + </script>
+28
src/applets/engine/audio/manifest.json
··· 1 + { 2 + "name": "diffuse/engine/audio", 3 + "entrypoint": "index.html", 4 + "actions": { 5 + "load": { 6 + "title": "Load", 7 + "description": "Load a given audio src", 8 + "params_schema": { 9 + "type": "string", 10 + "description": "String to be used as the audio `src`" 11 + } 12 + }, 13 + "pause": { 14 + "title": "Pause", 15 + "description": "Indicate the active audio should be paused", 16 + "params_schema": { 17 + "type": "null" 18 + } 19 + }, 20 + "play": { 21 + "title": "Play", 22 + "description": "Indicate the active audio should be playing", 23 + "params_schema": { 24 + "type": "null" 25 + } 26 + } 27 + } 28 + }
+65
src/applets/themes/pilot/ui/audio/applet.astro
··· 1 + <link 2 + rel="stylesheet" 3 + href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css" 4 + /> 5 + 6 + <div> 7 + <button>▶️</button> 8 + <progress max="100" value="0"></progress> 9 + </div> 10 + 11 + <style> 12 + @import "../../../../../styles/themes/pilot/variables.css"; 13 + 14 + div { 15 + /* 5% darker version */ 16 + background: oklch(from var(--delicate-cloud) calc(l - 0.1) c h); 17 + 18 + height: 100px; 19 + line-height: 100px; 20 + text-align: center; 21 + } 22 + </style> 23 + 24 + <script> 25 + import { applets } from "@web-applets/sdk"; 26 + 27 + interface State { 28 + isPlaying: boolean; 29 + } 30 + 31 + const context = applets.register<State>(); 32 + 33 + //////////////////////////////////////////// 34 + // Initial state 35 + //////////////////////////////////////////// 36 + context.data = { 37 + isPlaying: false, 38 + }; 39 + 40 + //////////////////////////////////////////// 41 + // Actions 42 + //////////////////////////////////////////// 43 + context.setActionHandler("set_is_playing", (isPlaying: boolean) => { 44 + context.data.isPlaying = isPlaying; 45 + render(); 46 + }); 47 + 48 + context.setActionHandler("set_progress", (progress: number) => { 49 + document.body.querySelector("progress").value = Math.round(progress * 100); 50 + render(); 51 + }); 52 + 53 + //////////////////////////////////////////// 54 + // DOM 55 + //////////////////////////////////////////// 56 + document.body.querySelector("button").onclick = () => { 57 + context.data = { isPlaying: !(context.data?.isPlaying ?? false) }; 58 + }; 59 + 60 + function render() { 61 + document.body.querySelector("button").innerText = context.data.isPlaying ? "⏸️" : "▶️"; 62 + } 63 + 64 + render(); 65 + </script>
+20
src/applets/themes/pilot/ui/audio/manifest.json
··· 1 + { 2 + "name": "diffuse/ui/audio", 3 + "entrypoint": "index.html", 4 + "actions": { 5 + "set_is_playing": { 6 + "title": "Set is-playing state", 7 + "description": "Indicate if audio is playing or not.", 8 + "params_schema": { 9 + "type": "boolean" 10 + } 11 + }, 12 + "set_progress": { 13 + "title": "Set progress", 14 + "description": "Indicate how far the audio has progressed.", 15 + "params_schema": { 16 + "type": "number" 17 + } 18 + } 19 + } 20 + }
-7
src/common/pages/manifest.ts
··· 1 - export function manifest(obj: Record<string, unknown>) { 2 - return function GET() { 3 - return new Response( 4 - JSON.stringify(obj), 5 - ); 6 - }; 7 - }
+8
src/content.config.ts
··· 1 + import { defineCollection, z } from "astro:content"; 2 + import { glob } from "astro/loaders"; 3 + 4 + const applets = defineCollection({ 5 + loader: glob({ pattern: "**/*.json", base: "./src/applets" }), 6 + }); 7 + 8 + export const collections = { applets };
+3 -4
src/layouts/applet.astro
··· 11 11 <title>{title}</title> 12 12 13 13 <style> 14 - html, 15 - body { 16 - margin: 0; 17 - } 14 + @import "../styles/reset.css"; 15 + @import "../styles/fonts.css"; 16 + @import "../styles/variables.css"; 18 17 </style> 19 18 </head> 20 19 <body>
+59
src/pages/[...applet].astro
··· 1 + --- 2 + import { getCollection } from "astro:content"; 3 + import AppletLayout from "../layouts/applet.astro"; 4 + 5 + // Generate static paths 6 + export async function getStaticPaths() { 7 + async function gen(path: string[]): Promise<{ 8 + applet: string; 9 + title: string; 10 + Component: (_props: Record<string, any>) => any; 11 + }> { 12 + let Applet; 13 + let manifest; 14 + 15 + if (path.length === 2) { 16 + Applet = await import(`../applets/${path[0]}/${path[1]}/applet.astro`); 17 + manifest = await import(`../applets/${path[0]}/${path[1]}/manifest.json`); 18 + } 19 + 20 + if (path.length === 4) { 21 + Applet = await import(`../applets/${path[0]}/${path[1]}/${path[2]}/${path[3]}/applet.astro`); 22 + manifest = await import( 23 + `../applets/${path[0]}/${path[1]}/${path[2]}/${path[3]}/manifest.json` 24 + ); 25 + } 26 + 27 + if (Applet === undefined || manifest === undefined) { 28 + throw new Error("Unsupported path length"); 29 + } 30 + 31 + return { 32 + applet: path.join("/"), 33 + title: manifest.default.name, 34 + Component: Applet.default, 35 + }; 36 + } 37 + 38 + const applets = await getCollection("applets"); 39 + const pages = await Promise.all( 40 + applets.map((applet) => { 41 + return gen(applet.id.split("/").slice(0, -1)); 42 + }), 43 + ); 44 + 45 + return pages.map(({ applet, Component, title }) => { 46 + return { 47 + params: { applet }, 48 + props: { Component, title }, 49 + }; 50 + }); 51 + } 52 + 53 + // Render props 54 + const { Component, title } = Astro.props; 55 + --- 56 + 57 + <AppletLayout title={title}> 58 + <Component /> 59 + </AppletLayout>
+21
src/pages/[...manifest].json.ts
··· 1 + import type { APIRoute } from "astro"; 2 + import { getCollection } from "astro:content"; 3 + 4 + // API Route 5 + export const GET: APIRoute = ({ params, props, request }) => { 6 + return new Response( 7 + JSON.stringify(props.manifest), 8 + ); 9 + }; 10 + 11 + // Generate static paths 12 + export async function getStaticPaths() { 13 + const applets = await getCollection("applets"); 14 + 15 + return applets.map((applet) => { 16 + return { 17 + params: { manifest: applet.id }, 18 + props: { manifest: applet.data }, 19 + }; 20 + }); 21 + }
-59
src/pages/engine/audio/index.astro
··· 1 - --- 2 - import Applet from "../../../layouts/applet.astro"; 3 - --- 4 - 5 - <Applet title="Diffuse engine/audio applet"> 6 - <div id="container"> 7 - <audio 8 - src="https://archive.org/download/lp_moonlight-sonata_ludwig-van-beethoven-frdric-chopin-alexand/disc1%2F01.02.%20Moonlight%20Sonata%20Op.%2027%2C%20No.%202%20In%20C%20Sharp%20Minor%3A%20Allegretto.mp3?tunnel=1" 9 - ></audio> 10 - </div> 11 - 12 - <script> 13 - import { applets } from "@web-applets/sdk"; 14 - 15 - interface State { 16 - isPlaying: boolean; 17 - progress: number; 18 - } 19 - 20 - const context = applets.register<State>(); 21 - const container = document.querySelector("#container"); 22 - const audio = document.querySelector("audio"); 23 - 24 - //////////////////////////////////////////// 25 - // Initial state 26 - //////////////////////////////////////////// 27 - context.data = { 28 - isPlaying: false, 29 - progress: 0, 30 - }; 31 - 32 - //////////////////////////////////////////// 33 - // Audio events 34 - //////////////////////////////////////////// 35 - audio.ontimeupdate = (event) => { 36 - const progress = 37 - isNaN(audio.duration) || audio.duration === 0 ? 0 : audio.currentTime / audio.duration; 38 - context.data = { ...context.data, progress }; 39 - }; 40 - 41 - audio.onpause = () => (context.data = { ...context.data, isPlaying: false }); 42 - audio.onplay = () => (context.data = { ...context.data, isPlaying: true }); 43 - 44 - //////////////////////////////////////////// 45 - // Actions 46 - //////////////////////////////////////////// 47 - context.setActionHandler("load", (src: string) => { 48 - audio.src = src; 49 - }); 50 - 51 - context.setActionHandler("play", () => { 52 - audio.play(); 53 - }); 54 - 55 - context.setActionHandler("pause", () => { 56 - audio.pause(); 57 - }); 58 - </script> 59 - </Applet>
-30
src/pages/engine/audio/manifest.json.ts
··· 1 - import { manifest } from "../../../common/pages/manifest.ts"; 2 - 3 - export const GET = manifest({ 4 - name: "diffuse/engine/audio", 5 - entrypoint: "index.html", 6 - actions: { 7 - load: { 8 - title: "Load", 9 - description: "Load a given audio src", 10 - params_schema: { 11 - type: "string", 12 - description: "String to be used as the audio `src`", 13 - }, 14 - }, 15 - pause: { 16 - title: "Pause", 17 - description: "Indicate the active audio should be paused", 18 - params_schema: { 19 - type: "null", 20 - }, 21 - }, 22 - play: { 23 - title: "Play", 24 - description: "Indicate the active audio should be playing", 25 - params_schema: { 26 - type: "null", 27 - }, 28 - }, 29 - }, 30 - });
+1 -1
src/pages/index.astro
··· 7 7 import "../styles/pages/index.css"; 8 8 9 9 const engines = [{ url: "engine/audio/", title: "Audio" }]; 10 - const themes = [{ url: "themes/sakura/", title: "Sakura" }]; 10 + const themes = [{ url: "themes/pilot/", title: "Pilot" }]; 11 11 --- 12 12 13 13 <Page title="Index">
+45 -4
src/pages/themes/sakura/index.astro src/pages/themes/pilot/index.astro
··· 4 4 5 5 <Page title="Diffuse Applets Usage Example"> 6 6 <!-- Theme applets --> 7 - <iframe src="ui/audio/" frameborder="0" style="width: 100%"></iframe> 7 + <iframe id="#applet__ui__audio" src="ui/audio/" frameborder="0" style="width: 100%"></iframe> 8 8 9 9 <!-- Other applets --> 10 - <iframe src="../../engine/audio/" frameborder="0" height="0" width="0"></iframe> 10 + <iframe id="#applet__engine__audio" src="../../engine/audio/" frameborder="0" height="0" width="0" 11 + ></iframe> 12 + 13 + <div>Testing</div> 11 14 </Page> 12 15 16 + <style is:global> 17 + @import "../../../styles/reset.css"; 18 + @import "../../../styles/fonts.css"; 19 + @import "../../../styles/variables.css"; 20 + @import "../../../styles/themes/pilot/variables.css"; 21 + </style> 22 + 13 23 <style> 14 - @import "../../../styles/pages/themes/sakura/variables.css"; 24 + /*********************************** 25 + * Fonts 26 + ***********************************/ 27 + :root { 28 + font-family: "Inter", sans-serif; 29 + font-size: var(--fs-base); 30 + } 31 + 32 + @supports (font-variation-settings: normal) { 33 + :root { 34 + font-family: "InterVariable", sans-serif; 35 + font-feature-settings: 36 + "ss03" 2, 37 + "ss02" 2; 38 + font-optical-sizing: auto; 39 + } 40 + } 15 41 16 42 body { 17 - background-color: var(--reading-tea-leaves); 43 + background-color: var(--delicate-cloud); 44 + color: var(--made-in-the-shade); 45 + overflow: hidden; 46 + } 47 + 48 + /*********************************** 49 + * Applets | Position 50 + ***********************************/ 51 + #applet__ui__audio { 52 + } 53 + 54 + /* Position engines outside the viewframe (no UI for these) */ 55 + #applet__engine__audio { 56 + left: 110vw; 57 + position: absolute; 58 + top: 110vh; 18 59 } 19 60 </style> 20 61
-66
src/pages/themes/sakura/ui/audio/index.astro
··· 1 - --- 2 - import Applet from "../../../../../layouts/applet.astro"; 3 - --- 4 - 5 - <Applet title="Diffuse ui/audio applet"> 6 - <div> 7 - <button>▶️</button> 8 - <progress max="100" value="0"></progress> 9 - </div> 10 - 11 - <style> 12 - @import "../../../../../styles/pages/themes/sakura/variables.css"; 13 - 14 - div { 15 - /* 5% darker version */ 16 - background: oklch(from var(--reading-tea-leaves) calc(l - 0.05) c h); 17 - 18 - height: 100px; 19 - line-height: 100px; 20 - text-align: center; 21 - } 22 - </style> 23 - 24 - <script> 25 - import { applets } from "@web-applets/sdk"; 26 - 27 - interface State { 28 - isPlaying: boolean; 29 - } 30 - 31 - const context = applets.register<State>(); 32 - 33 - //////////////////////////////////////////// 34 - // Initial state 35 - //////////////////////////////////////////// 36 - context.data = { 37 - isPlaying: false, 38 - }; 39 - 40 - //////////////////////////////////////////// 41 - // Actions 42 - //////////////////////////////////////////// 43 - context.setActionHandler("set_is_playing", (isPlaying: boolean) => { 44 - context.data.isPlaying = isPlaying; 45 - render(); 46 - }); 47 - 48 - context.setActionHandler("set_progress", (progress: number) => { 49 - document.body.querySelector("progress").value = Math.round(progress * 100); 50 - render(); 51 - }); 52 - 53 - //////////////////////////////////////////// 54 - // DOM 55 - //////////////////////////////////////////// 56 - document.body.querySelector("button").onclick = () => { 57 - context.data = { isPlaying: !(context.data?.isPlaying ?? false) }; 58 - }; 59 - 60 - function render() { 61 - document.body.querySelector("button").innerText = context.data.isPlaying ? "⏸️" : "▶️"; 62 - } 63 - 64 - render(); 65 - </script> 66 - </Applet>
-22
src/pages/themes/sakura/ui/audio/manifest.json.ts
··· 1 - import { manifest } from "../../../../../common/pages/manifest.ts"; 2 - 3 - export const GET = manifest({ 4 - name: "diffuse/ui/audio", 5 - entrypoint: "index.html", 6 - actions: { 7 - set_is_playing: { 8 - title: "Set is-playing state", 9 - description: "Indicate if audio is playing or not.", 10 - params_schema: { 11 - type: "boolean", 12 - }, 13 - }, 14 - set_progress: { 15 - title: "Set progress", 16 - description: "Indicate how far the audio has progressed.", 17 - params_schema: { 18 - type: "number", 19 - }, 20 - }, 21 - }, 22 - });
-5
src/styles/pages/themes/sakura/variables.css
··· 1 - :root { 2 - /* Colors */ 3 - --reading-tea-leaves: #7b6467; 4 - --sakura: #e1b3b9; 5 - }
+26
src/styles/themes/pilot/variables.css
··· 1 + :root { 2 + /* Colors */ 3 + /* https://farbvelo.elastiq.ch/?s=eyJzIjoiZTBjNjIyMTdiNTcxZSIsImEiOjYsImNnIjo0LCJoZyI6dHJ1ZSwiaGIiOmZhbHNlLCJobyI6ZmFsc2UsImhjIjpmYWxzZSwiaHQiOmZhbHNlLCJiIjpmYWxzZSwicCI6MC4xNzUsIm1kIjo2MCwiY20iOiJsYWIiLCJmIjoiTGVnYWN5IiwiYyI6ImhzbHV2Iiwic2MiOmZhbHNlLCJidyI6dHJ1ZSwiYWgiOmZhbHNlLCJpdSI6IiIsImxtIjp0cnVlLCJzbSI6ZmFsc2UsImN2IjoiaGV4IiwicW0iOiJhcnQtcGFsZXR0ZSIsIm5sIjoiYmVzdE9mIn0= */ 4 + --moonscape: #7f6c71; 5 + --grandma’s-pink-tiles: #e1bac0; 6 + --cinderella: #f8d1c6; 7 + --young-apricot: #f8d7b6; 8 + --cereal-flake: #f0d8ad; 9 + --oatmeal: #cdc5b9; 10 + 11 + /* https://farbvelo.elastiq.ch/?s=eyJzIjoiZmZjY2JkZDg2ZjEzYiIsImEiOjYsImNnIjo0LCJoZyI6dHJ1ZSwiaGIiOmZhbHNlLCJobyI6ZmFsc2UsImhjIjpmYWxzZSwiaHQiOmZhbHNlLCJiIjpmYWxzZSwicCI6MC4xNzgzMDcwODQxNjMzNDY2LCJtZCI6NjAsImNtIjoibGFiIiwiZiI6IkxlZ2FjeSIsImMiOiJoc2x1diIsInNjIjpmYWxzZSwiYnciOnRydWUsImFoIjpmYWxzZSwiaXUiOiIiLCJsbSI6dHJ1ZSwic20iOmZhbHNlLCJjdiI6ImhzbCIsInFtIjoiYXJ0LXBhbGV0dGUiLCJubCI6ImJlc3RPZiJ9 */ 12 + --made-in-the-shade: #67717c; 13 + --misty-mountains: #b8cce0; 14 + --lucid-dreams: #c7e6f4; 15 + --icy-breeze: #c2eff1; 16 + --crushed-ice: #bdf5ed; 17 + --water-leaf: #b7efe7; 18 + 19 + /* https://farbvelo.elastiq.ch/?s=eyJzIjoiODJiN2FjMjU1ODRiOCIsImEiOjYsImNnIjo0LCJoZyI6dHJ1ZSwiaGIiOmZhbHNlLCJobyI6ZmFsc2UsImhjIjpmYWxzZSwiaHQiOmZhbHNlLCJiIjpmYWxzZSwicCI6MC4yMTkxOTgyMDcxNzEzMTQ3LCJtZCI6NjAsImNtIjoibGFiIiwiZiI6IkxlZ2FjeSIsImMiOiJoc2x1diIsInNjIjpmYWxzZSwiYnciOnRydWUsImFoIjpmYWxzZSwiaXUiOiIiLCJsbSI6dHJ1ZSwic20iOmZhbHNlLCJjdiI6ImhleCIsInFtIjoiYXJ0LXBhbGV0dGUiLCJubCI6ImJlc3RPZiJ9 */ 20 + --wizards-brew: #9d8bb3; 21 + --innocent-snowdrop: #cec0fa; 22 + --foggy-plateau: #d5d2fb; 23 + --puffy-cloud: #dce3fb; 24 + --diamond-white: #e1f4fb; 25 + --delicate-cloud: #d9dbe4; 26 + }