separated code into extensions

This commit is contained in:
Leon van Kammen 2025-10-23 19:20:50 +02:00
parent 33e2b4c335
commit 520b8cd2c5
7 changed files with 177 additions and 50 deletions

46
lib/AsyncEmitter.js Normal file
View file

@ -0,0 +1,46 @@
/*
* async Event Emitter
*
* // Example usage
* const emitter = new AsyncEmitter();
*
* // Register some async listeners
* emitter.on("data", async (msg) => {
* console.log("Listener 1 starting...");
* await new Promise((r) => setTimeout(r, 1000));
* console.log("Listener 1 done:", msg);
* });
*
* emitter.on("data", async (msg) => {
* console.log("Listener 2 starting...");
* await new Promise((r) => setTimeout(r, 500));
* console.log("Listener 2 done:", msg);
* });
*
* (async () => {
* console.log("Emitting...");
* await emitter.emit("data", "Hello async world!");
* console.log("All listeners finished.");
* })();
*/
class AsyncEmitter {
constructor() {
this.listeners = new Map();
}
on(event, listener) {
if (!this.listeners.has(event)) this.listeners.set(event, []);
this.listeners.get(event).push( listener );
}
async emit(event, ...args) {
const listeners = this.listeners.get(event) || [];
if( document.location.hostname == 'localhost' ) console.info(`emit(${event})`)
for (const fn of listeners) {
await fn(...args); // <-- Waits for each listener to finish
}
}
}
export {AsyncEmitter}

View file

@ -1,42 +1,21 @@
import {AsyncEmitter} from "./AsyncEmitter.js"
function widget(){
return new Proxy({
const me = new AsyncEmitter()
me.player = document.querySelector("#player"),
me.src = document.location.search.substr(1),
me.ext = {}
backend: null,
player: document.querySelector("#player"),
src: document.location.search.substr(1),
ext: {},
return new Proxy( me, {
get(me,k){ return me[k] },
init(opts){
for( var i in opts) this[i] = opts[i]
},
play(){
// set URL
player.setAttribute("gltf-model", `url(${widget.src})` )
document.querySelector("#btn_play").style.display = 'none'
let script = document.createElement("script")
script.src = "https://aframe.io/releases/1.7.0/aframe.min.js"
document.head.appendChild(script)
script = document.createElement("script")
script.src = "backend.xrforge.js"
document.head.appendChild(script)
set(me,k,v){
me[k] = v
return true
}
},
{
get(me,k){ return me[k] },
set(me,k,v){
me[k] = v
return true
}
})
})
}
export {widget}

34
lib/widget/play.js Normal file
View file

@ -0,0 +1,34 @@
export default function extension(widget){
const ext = {
async init(){
// let other extensions set this.src if needed
await widget.emit("init.play", this)
if( widget.src ){
document.querySelector('#scene').setAttribute("gltf-model",`url(${widget.src})`)
}
this.initButton()
},
initButton(){
const btn_play = document.querySelector("#btn_play")
btn_play.addEventListener("click", async () => {
btn_play.style.display = 'none' // hide button
widget.emit("play")
})
}
}
widget.ext.play = ext
// register async listeners
widget.on("init", ext.init.bind(ext) )
}

26
lib/widget/thumb.js Normal file
View file

@ -0,0 +1,26 @@
export default function extension(widget){
const ext = {
src: "",
init: async () => {
// let other extensions set this.src if needed
await widget.emit("init.thumb", widget.ext.thumb )
if( widget.ext.thumb.src ){
document.body.style.background = `url(${widget.ext.thumb.src}) no-repeat center / cover`
}
}
}
widget.ext.thumb = ext
// register async listeners
widget.on("init", ext.init.bind(ext) )
}

View file

@ -1,24 +1,61 @@
import {widget} from './../widget.js'
import {widget as Widget} from './../widget.js'
import {inferSource} from './util.js'
const btn_play = document.querySelector("#btn_play")
btn_play.addEventListener("click", () => {
btn_play.style.display = 'none' // hide button
import('aframe')
})
// extensions
import {default as thumb} from './../widget/thumb.js'
import {default as play} from './../widget/play.js'
let src = document.location.search.substr(1)
let cover = src
const img = /\.(png|jpg|webp)/
const widget = Widget()
// init extensions
thumb(widget)
play(widget)
// init xrforge extension
widget.ext.xrforge = {
// manyfold URLs need to be de-obfuscated
if( src.match(img) ){
cover = await inferSource(src)
src = cover.replace(img,".glb")
}
console.dir({cover,src})
// in case of Manyfold backend, a thumbnail is passed (not gltf src e.g.)
// so we will infer the source-url based on the thumbnail
widget.on('init.thumb', async (thumb) => {
let src = widget.src
const img = /\.(png|jpg|webp)/
document.querySelector('#scene').setAttribute("gltf-model",`url(${src})`)
document.body.style.background = `url(${cover}) no-repeat center / cover`
if( src.match(img) ){
thumb.src = src
let inferredSrc = await inferSource(src)
widget.src = inferredSrc.replace(img,".glb")
}
})
widget.on("play", async () => {
// initialize a specialized build of THREE/AFRAME
const AFRAME = await import('aframe')
window.THREE.DRACOLoader = await import('three/examples/jsm/loaders/DRACOLoader.js')
window.THREE.FBXLoader = await import('three/examples/jsm/loaders/FBXLoader.js')
window.THREE.USDZLoader = await import('three/examples/jsm/loaders/USDZLoader.js')
window.THREE.ColladaLoader = await import('three/examples/jsm/loaders/ColladaLoader.js')
window.THREE.MTLLoader = await import('three/examples/jsm/loaders/MTLLoader.js')
window.THREE.GLTFExporter = await import('three/examples/jsm/exporters/GLTFExporter.js')
// optional utils
window.zipjs = await import("@zip.js/zip.js")
window.webdav = await import("webdav")
window.rs = await import("remotestoragejs")
window.trystero = await import("trystero")
// include xrsh (remote for now, until the final integration is more clear)
const script = document.createElement("script")
script.src = "https://xrsh.isvery.ninja/xrsh.js"
script.addEventListener("load", function(){
const ent = document.createElement("a-entity")
ent.setAttribute("isoterminal","minimized:true")
ent.setAttribute("position","0 1.6 -0.3")
document.querySelector("a-scene").appendChild(ent)
})
document.body.appendChild(script)
})
await widget.emit("init")
console.dir(widget)

View file

@ -10,7 +10,13 @@
"license": "",
"description": "",
"dependencies": {
"@needle-tools/three-animation-pointer": "^1.0.7",
"@zip.js/zip.js": "^2.8.8",
"aframe": "^1.7.1",
"xrf": "^0.0.1"
"remotestoragejs": "^2.0.0-beta.8",
"trystero": "^0.22.0",
"webdav": "^5.8.0",
"xrf": "^0.0.1",
"xrsh": "^0.0.5"
}
}

View file

@ -11,7 +11,6 @@
# nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs.buildPackages; [
nodejs_20
monolith
bun