// this is a highlevel way of loading buildless 'apps' (a collection of js components) AFRAME.registerComponent('app', { schema:{ "uri":{ type:"string"} }, init: function() { let id = this.data.uri.split("/").pop() // 'a/b/c/mycom.js' => 'mycom.js' let component = id.split(".js").shift() // 'mycom.js' => 'mycom' let mount = () => { let entity = document.createElement("a-entity") this.el.setAttribute(component,this.data) } if( AFRAME.components[component] || document.head.querySelector(`script#${id}`) ) mount() else this.require([ this.data.uri ]).then( mount ).catch(console.error) }, // usage: require(["./app/foo.js"]) // require({foo: "https://foo.com/foo.js"}) require: AFRAME.AComponent.prototype.require = function(packages){ let deps = [] if( !packages.map ) packages = Object.values(packages) packages.map( (package) => { let id = package.split("/").pop() if( !document.head.querySelector(`script#${id}`) ){ let p = new Promise( (resolve,reject) => { let script = document.createElement("script") script.id = id script.src = package script.onload = () => resolve() script.onerror = (e) => reject(e) document.head.appendChild(script) }) deps.push(p) } }) return Promise.all(deps) } }) // 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' AFRAME.AComponent.prototype.initComponent = function(initComponent){ return function(){ this.el.emit( this.attrName, this) return initComponent.apply(this,arguments) } }( AFRAME.AComponent.prototype.initComponent) // monkeypatching updateProperties will detect a component config like: // dom: { // html: `

hello

`, // css: `#hello {color:red}`, // events: ['click'] // } // and use it to create a reactive DOM-component (using native javascript Proxy) // which delegates all related DOM-events AND data-changes back to the AFRAME component AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ return function(){ updateProperties.apply(this,arguments) if( this.dom && this.data){ tasks = { generateUniqueId: () => { this.el.uid = String(Math.random()).substr(2) return tasks }, ensureOverlay: () => { let overlay = document.querySelector('#overlay') if( !overlay ){ overlay = document.createElement('div') overlay.id = "#overlay" document.body.appendChild(overlay) document.querySelector("a-scene").setAttribute("webxr","overlayElement:#overlay") } tasks.overlay = overlay return 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.innerHTML = this.dom.html(this) this.data = reactify( this.dom.el, this.el ) this.dom.events.map( (e) => this.el.dom.addEventListener(e, (ev) => this.el.emit(e,ev) ) ) return tasks }, addCSS: () => { if( this.dom.css && !document.head.querySelector(`style#${this.attrName}`) ){ document.head.innerHTML += `` } return tasks }, scaleDOMvsXR: () => { if( this.dom.scale ) this.el.setAttribute('scale',`${this.dom.scale} ${this.dom.scale} ${this.dom.scale}`) return tasks }, addModalFunctions: () => { this.el.close = () => { this.el.dom.remove() this.el.removeAttribute("html") } this.el.toggleFold = () => { this.el.dom.querySelector(".modal").classList.toggle('fold') this.el.dom.querySelector('.top .fold').innerText = this.el.dom.querySelector('.modal').className.match(/fold/) ? '▢' : '_' } return tasks }, } tasks .generateUniqueId() .ensureOverlay() .addCSS() .createReactiveDOMElement() .scaleDOMvsXR() .addModalFunctions() // finally lets add the bad boy to the DOM tasks.overlay.appendChild(this.el.dom) } } }( AFRAME.AComponent.prototype.updateProperties) // // base CSS for XRSH apps // // limitations / some guidelines for html-mesh compatibility: // * no icon libraries (favicon e.g.) // * 'border-radius: 2px 3px 4px 5px' (applies 2px to all corners) // document.head.innerHTML += ` `