// 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 += `
`