From 375f30ef7056bc1cda81c696688bb7c8789a9f3e Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Thu, 7 Mar 2024 17:34:37 +0100 Subject: [PATCH] allow global app components --- com/app.js | 94 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/com/app.js b/com/app.js index 111fd92..c79af2b 100644 --- a/com/app.js +++ b/com/app.js @@ -5,11 +5,13 @@ AFRAME.app = new Proxy({ order:0, - add(component, entity){ + components: {}, // component-strings in this array are automatically + // added to each app + add(component, entity){ // categorize by component to prevent similar apps loading duplicate dependencies simultaniously this[component] = this[component] || [] this[component].push(entity) - entity.data.order = entity.data.order || this.count() + entity.data.order = entity.data.order || this.count() }, count(){ let n = 0 @@ -34,7 +36,7 @@ AFRAME.app = new Proxy({ * This is the abstract 'app' component */ -AFRAME.registerComponent('app', { +appComponent = { schema:{ "uri":{ type:"string"} }, @@ -43,8 +45,12 @@ AFRAME.registerComponent('app', { "app:ready": function(){ let {id,component,type} = this.parseAppURI(this.data.uri) AFRAME.app[component].map( (app) => { - if( !app.el.getAttribute(component) ) app.el.setAttribute(component,app.data) - }) + if( !app.el.getAttribute(component) ){ + if( AFRAME.components[ component ] ){ + app.el.setAttribute(component,app.data) + }else console.warn(`${component} was not fully downloaded yet (${app.data.uri})`) + } + }) }, "requires:ready": function(){ let {id,component,type} = this.parseAppURI(this.data.uri) @@ -55,16 +61,16 @@ AFRAME.registerComponent('app', { app.el.emit('ready') app.readyFired = true },400) // big js scripts need some parsing time - }) + }) }, }, - init: function() { + init: function() { let {id,component,type} = this.parseAppURI(this.data.uri) let sel = `script#${component}` if( AFRAME.app[component] || AFRAME.components[component] || document.head.querySelector(sel) ) return AFRAME.app.add(component,this) AFRAME.app.add(component,this) - this.require([ this.data.uri ], 'app:ready') + this.require([ this.data.uri ], 'app:ready') }, parseAppURI: AFRAME.AComponent.prototype.parseAppURI = function(uri){ @@ -84,7 +90,7 @@ AFRAME.registerComponent('app', { let id = package.split("/").pop() // prevent duplicate requests if( AFRAME.required[id] ) return - AFRAME.required[id] = true + AFRAME.required[id] = true if( !document.head.querySelector(`script#${id}`) ){ let {component,type} = this.parseAppURI(package) @@ -111,21 +117,24 @@ AFRAME.registerComponent('app', { }catch(e){ console.error(`package ${package} could not be retrieved..aborting :(`); throw e; } }) Promise.all(deps).then( () => { - this.el.emit( readyEvent || 'requireReady', packages) + this.el.emit( readyEvent || 'requireReady', packages) }) } -}) +} + +AFRAME.registerComponent('app', appComponent) +AFRAME.registerComponent('com', appComponent) /* * Here are monkeypatched AFRAME component prototype functions * - * monkeypatching initComponent will trigger events when components - * are initialized (that way apps can react to attached components) - * basically, in both situations: - * + * monkeypatching initComponent will trigger events when components + * are initialized (that way apps can react to attached components) + * basically, in both situations: + * * - * + * * event 'foo' will be triggered as both entities (in)directly require component 'foo' */ @@ -151,22 +160,16 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ overlay.id = "overlay" document.body.appendChild(overlay) document.querySelector("a-scene").setAttribute("webxr","overlayElement:#overlay") -// let menu = document.createElement('div') -// menu.id = 'iconmenu' -// document.body.appendChild(menu) } -// // add menu button -// if( this.manifest && this.manifest.icons ){ -// let btn = document.createElement('button') -// btn.app = this -// if( this.manifest.icons.length ){ -// btn.innerHTML = `` -// }else btn.innerText = this.manifest.short_name -// btn.setAttribute("alt", this.manifest.name ) -// btn.addEventListener('click', (e) => this.el.emit('launcher',{}) ) -// document.querySelector("#iconmenu").appendChild(btn) -// } + const reactify = (el,aframe) => new Proxy(this.data,{ + get(me,k,v) { return me[k] + }, + set(me,k,v){ + me[k] = v + aframe.emit(k,{el,k,v}) + } + }) // reactify components with dom-definition if( this.data.uri && this.dom && !this.el.dom ){ @@ -174,14 +177,6 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ tasks = { createReactiveDOMElement: () => { - const reactify = (el,aframe) => new Proxy(this.data,{ - get(me,k,v) { return me[k] - }, - set(me,k,v){ - me[k] = v - aframe.emit(k,{el,k,v}) - } - }) this.el.dom = document.createElement('div') this.el.dom.className = this.parseAppURI(this.data.uri).component this.el.dom.innerHTML = this.dom.html(this) @@ -206,12 +201,19 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ setupListeners: () => { this.scene.addEventListener('apps:2D', () => this.el.setAttribute('visible', false) ) this.scene.addEventListener('apps:XR', () => { - this.el.setAttribute('visible', true) + this.el.setAttribute('visible', true) this.el.setAttribute("html",`html:#${this.el.uid}; cursor:#cursor`) }) return tasks }, + initAutoComponents: () => { + for ( let i in AFRAME.app.components ) { + this.el.setAttribute( i, AFRAME.app.components[i] ) + } + return tasks + }, + triggerKeyboardForInputs: () => { // https://developer.oculus.com/documentation/web/webxr-keyboard ; [...this.el.dom.querySelectorAll('[type=text]')].map( (input) => { @@ -232,16 +234,24 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ .scaleDOMvsXR() .triggerKeyboardForInputs() .setupListeners() + .initAutoComponents() document.querySelector('#overlay').appendChild(this.el.dom) this.el.emit('DOMready',{el: this.el.dom}) - } + }else this.data = reactify( this.el, this.el ) + // assign unique app id if( !this.el.uid ) this.el.uid = '_'+String(Math.random()).substr(10) - if( this.requires ) this.require( this.requires, 'requires:ready' ) - else this.el.emit('requires:ready') + // require coms + let requires = {} + for ( let i in AFRAME.app.components ) { + if( !AFRAME.components[i] ) requires[i] = AFRAME.app.components[i] + } + if( this.requires ) requires = {...requires, ...this.requires } + if( Object.values(requires).length ) this.require( requires, 'requires:ready' ) + else this.el.emit('requires:ready' ) // mark app as being initialized this.isApp = true