tangled
alpha
login
or
join now
tokono.ma
/
diffuse-applets
0
fork
atom
Experiment to rebuild Diffuse using web applets.
0
fork
atom
overview
issues
pulls
pipelines
feat: Better applet connective tissue
Steven Vandevelde
1 year ago
329f7610
027475ee
+126
-41
6 changed files
expand all
collapse all
unified
split
astro.config.mjs
deno.json
deno.lock
src
pages
engines
audio
index.astro
index.astro
interface
audio
index.astro
+5
astro.config.mjs
···
1
1
+
import { defineConfig } from "astro/config";
2
2
+
3
3
+
export default defineConfig({
4
4
+
trailingSlash: "always",
5
5
+
});
+4
-3
deno.json
···
1
1
{
2
2
"imports": {
3
3
-
"@web-applets/sdk": "npm:@web-applets/sdk@0.2.0-alpha.8",
4
4
-
"astro": "npm:astro@^5.4.1"
3
3
+
"@web-applets/sdk": "npm:@web-applets/sdk@0.2.0-alpha.12",
4
4
+
"astro": "npm:astro@^5.4.1",
5
5
+
"spellcaster": "npm:spellcaster@^5.0.2"
5
6
},
6
7
"tasks": {
7
8
"build": "astro build",
8
8
-
"dev": "astro preview"
9
9
+
"dev": "astro dev"
9
10
},
10
11
"compilerOptions": {
11
12
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
+17
-6
deno.lock
···
1
1
{
2
2
"version": "4",
3
3
"specifiers": {
4
4
-
"npm:@web-applets/sdk@0.2.0-alpha.8": "0.2.0-alpha.8",
4
4
+
"npm:@web-applets/sdk@0.2.0-alpha.12": "0.2.0-alpha.12",
5
5
"npm:asto@*": "0.1.0",
6
6
"npm:asto@0.1.0": "0.1.0",
7
7
"npm:astro@5.4.2": "5.4.2_vite@6.2.1_zod@3.24.2",
8
8
"npm:astro@^5.4.1": "5.4.2_vite@6.2.1_zod@3.24.2",
9
9
-
"npm:create-astro@latest": "4.11.1"
9
9
+
"npm:create-astro@latest": "4.11.1",
10
10
+
"npm:spellcaster@^5.0.2": "5.0.2"
10
11
},
11
12
"npm": {
12
13
"@astrojs/cli-kit@0.4.1": {
···
484
485
"@ungap/structured-clone@1.3.0": {
485
486
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="
486
487
},
487
487
-
"@web-applets/sdk@0.2.0-alpha.8": {
488
488
-
"integrity": "sha512-rEAAUlWy9IN8lNh7SGbnOPVGEOhtC7rfsCHK8Sc/sNhqTWY1Vwwiq6tsb+O2xHjibfWq3aJAQ7G8roR1Rcmzlg=="
488
488
+
"@web-applets/sdk@0.2.0-alpha.12": {
489
489
+
"integrity": "sha512-LCGHB/7q3h5EZReZAK9zrrODBDsFbJaNXhEhGFF8ga2JKXKnBCkeF3fYg7tTVsIOLY52dG7JkUo2911hEABfhw=="
489
490
},
490
491
"acorn@8.14.1": {
491
492
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="
···
1979
1980
"signal-exit@3.0.7": {
1980
1981
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
1981
1982
},
1983
1983
+
"signal-polyfill@0.1.2": {
1984
1984
+
"integrity": "sha512-HT9d+L9NMiTzMxb/tU2Baym6129ROyRETSjvchvSkQa7wN0+SrG/IUlsaBLqKn2c+4mlze6CgQBEvgBjxOpiaQ=="
1985
1985
+
},
1982
1986
"simple-swizzle@0.2.2": {
1983
1987
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
1984
1988
"dependencies": [
···
2003
2007
},
2004
2008
"space-separated-tokens@2.0.2": {
2005
2009
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="
2010
2010
+
},
2011
2011
+
"spellcaster@5.0.2": {
2012
2012
+
"integrity": "sha512-suCHnQlCyXAV1OCrL0jEjE8lCK+f2bfmnvBfIqkG6Q3fNiQ7mAaeXtSyEKCI5p2ifSieC5bS/59EcIfDh5PWMA==",
2013
2013
+
"dependencies": [
2014
2014
+
"signal-polyfill"
2015
2015
+
]
2006
2016
},
2007
2017
"sprintf-js@1.0.3": {
2008
2018
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
···
2308
2318
},
2309
2319
"workspace": {
2310
2320
"dependencies": [
2311
2311
-
"npm:@web-applets/sdk@0.2.0-alpha.8",
2312
2312
-
"npm:astro@^5.4.1"
2321
2321
+
"npm:@web-applets/sdk@0.2.0-alpha.12",
2322
2322
+
"npm:astro@^5.4.1",
2323
2323
+
"npm:spellcaster@^5.0.2"
2313
2324
]
2314
2325
}
2315
2326
}
+26
-5
src/pages/engines/audio/index.astro
···
12
12
<script>
13
13
import { applets } from "@web-applets/sdk";
14
14
15
15
-
const context = applets.register();
15
15
+
interface State {
16
16
+
isPlaying: boolean;
17
17
+
progress: number;
18
18
+
}
19
19
+
20
20
+
const context = applets.register<State>();
16
21
const container = document.querySelector("#container");
17
22
const audio = document.querySelector("audio");
18
23
24
24
+
////////////////////////////////////////////
25
25
+
// Initial state
26
26
+
////////////////////////////////////////////
27
27
+
context.data = {
28
28
+
isPlaying: false,
29
29
+
progress: 0,
30
30
+
};
31
31
+
32
32
+
////////////////////////////////////////////
33
33
+
// Audio events
34
34
+
////////////////////////////////////////////
19
35
audio.ontimeupdate = (event) => {
20
20
-
context.data = {
21
21
-
progress:
22
22
-
isNaN(audio.duration) || audio.duration === 0 ? 0 : audio.currentTime / audio.duration,
23
23
-
};
36
36
+
const progress =
37
37
+
isNaN(audio.duration) || audio.duration === 0 ? 0 : audio.currentTime / audio.duration;
38
38
+
context.data = { ...context.data, progress };
24
39
};
25
40
41
41
+
audio.onpause = () => (context.data = { ...context.data, isPlaying: false });
42
42
+
audio.onplay = () => (context.data = { ...context.data, isPlaying: true });
43
43
+
44
44
+
////////////////////////////////////////////
45
45
+
// Actions
46
46
+
////////////////////////////////////////////
26
47
context.setActionHandler("load", (src: string) => {
27
48
audio.src = src;
28
49
});
+52
-22
src/pages/index.astro
···
3
3
---
4
4
5
5
<Page title="Diffuse Applets Usage Example">
6
6
-
<iframe src="engines/audio" frameborder="0" height="0" width="0"></iframe>
7
7
-
<iframe src="interface/audio" frameborder="0" style="width: 100%"></iframe>
6
6
+
<iframe src="engines/audio/" frameborder="0" height="0" width="0"></iframe>
7
7
+
<iframe src="interface/audio/" frameborder="0" style="width: 100%"></iframe>
8
8
</Page>
9
9
+
10
10
+
<style>
11
11
+
body {
12
12
+
background: darkgray;
13
13
+
}
14
14
+
</style>
9
15
10
16
<script>
11
17
import { applets } from "@web-applets/sdk";
18
18
+
import { effect, signal } from "spellcaster/spellcaster.js";
12
19
13
13
-
async function applet(src: string, opts: { setHeight?: boolean } = {}) {
14
14
-
const frame: HTMLIFrameElement = document.querySelector(`[src="${src}"]`);
15
15
-
16
16
-
if (opts.setHeight) {
17
17
-
frame.addEventListener(
18
18
-
"load",
19
19
-
() => {
20
20
-
frame.height = frame.contentWindow.document.body.scrollHeight + "px";
21
21
-
},
22
22
-
{ once: true },
23
23
-
);
24
24
-
}
25
25
-
26
26
-
return await applets.connect(frame.contentWindow);
27
27
-
}
28
28
-
20
20
+
////////////////////////////////////////////
21
21
+
// 🗂️ Applet index
22
22
+
////////////////////////////////////////////
29
23
const index = {
30
24
engines: {
31
25
audio: await applet("engines/audio"),
···
35
29
},
36
30
};
37
31
38
38
-
// Connect the applets
32
32
+
////////////////////////////////////////////
33
33
+
// 🔮 Signals & effects
34
34
+
////////////////////////////////////////////
35
35
+
const [progress, setProgress] = signal(0);
36
36
+
const [isPlaying, setIsPlaying] = signal(false);
37
37
+
38
38
+
effect(() => index.interface.audio.sendAction("set_is_playing", isPlaying()));
39
39
+
effect(() => index.interface.audio.sendAction("set_progress", progress()));
40
40
+
41
41
+
////////////////////////////////////////////
42
42
+
// ⚡ Connect applets
43
43
+
////////////////////////////////////////////
44
44
+
index.engines.audio.ondata = (event) => {
45
45
+
// NOTE: Instead of sending actions to the audio interface every time
46
46
+
// this event is dispatched, which is at least every second when
47
47
+
// audio is playing, we use signals and effects to manage that.
48
48
+
setIsPlaying(event.data.isPlaying);
49
49
+
setProgress(event.data.progress);
50
50
+
};
51
51
+
39
52
index.interface.audio.ondata = (event) => {
40
53
if (event.data.isPlaying) {
41
54
index.engines.audio.sendAction("play", null);
···
44
57
}
45
58
};
46
59
47
47
-
index.engines.audio.ondata = (event) => {
48
48
-
index.interface.audio.sendAction("set_progress", event.data.progress);
49
49
-
};
60
60
+
////////////////////////////////////////////
61
61
+
// 🛠️ Applet initialiser
62
62
+
////////////////////////////////////////////
63
63
+
async function applet(src: string, opts: { setHeight?: boolean } = {}) {
64
64
+
const frame: HTMLIFrameElement = document.querySelector(
65
65
+
`[src="${src}${src.endsWith("/") ? "" : "/"}"]`,
66
66
+
);
67
67
+
68
68
+
if (opts.setHeight) {
69
69
+
frame.addEventListener(
70
70
+
"load",
71
71
+
() => {
72
72
+
frame.height = frame.contentWindow.document.body.scrollHeight + "px";
73
73
+
},
74
74
+
{ once: true },
75
75
+
);
76
76
+
}
77
77
+
78
78
+
return await applets.connect(frame.contentWindow);
79
79
+
}
50
80
</script>
+22
-5
src/pages/interface/audio/index.astro
···
19
19
20
20
const context = applets.register<State>();
21
21
22
22
+
////////////////////////////////////////////
23
23
+
// Initial state
24
24
+
////////////////////////////////////////////
25
25
+
context.data = {
26
26
+
isPlaying: false,
27
27
+
};
28
28
+
29
29
+
////////////////////////////////////////////
30
30
+
// Actions
31
31
+
////////////////////////////////////////////
22
32
context.setActionHandler("set_is_playing", (isPlaying: boolean) => {
23
23
-
context.data = { ...context.data, isPlaying };
33
33
+
context.data.isPlaying = isPlaying;
34
34
+
render();
24
35
});
25
36
26
37
context.setActionHandler("set_progress", (progress: number) => {
27
38
document.body.querySelector("progress").value = Math.round(progress * 100);
39
39
+
render();
28
40
});
29
41
30
30
-
context.ondata = () => {
31
31
-
document.body.querySelector("button").innerText = context.data.isPlaying ? "⏸️" : "▶️";
32
32
-
};
33
33
-
42
42
+
////////////////////////////////////////////
43
43
+
// DOM
44
44
+
////////////////////////////////////////////
34
45
document.body.querySelector("button").onclick = () => {
35
46
context.data = { isPlaying: !(context.data?.isPlaying ?? false) };
36
47
};
48
48
+
49
49
+
function render() {
50
50
+
document.body.querySelector("button").innerText = context.data.isPlaying ? "⏸️" : "▶️";
51
51
+
}
52
52
+
53
53
+
render();
37
54
</script>
38
55
</Applet>