wip: iconmenu is now an app too

This commit is contained in:
Leon van Kammen 2024-01-22 11:13:41 +00:00
parent f1254d7fed
commit ad2fa285a0
8 changed files with 73 additions and 97 deletions

View File

@ -37,7 +37,7 @@ AFRAME.registerComponent('launcher', {
scale: 3, scale: 3,
events: ['click'], events: ['click'],
html: (me) => `<div> html: (me) => `<div>
<div class="iconmenu"></div> <div id="iconmenu"></div>
</div>`, </div>`,
css: `#iconmenu { css: `#iconmenu {
@ -53,6 +53,7 @@ AFRAME.registerComponent('launcher', {
padding-bottom: 72px; padding-bottom: 72px;
box-sizing: border-box; box-sizing: border-box;
pointer-events: none; pointer-events: none;
visibility: visible !important;
} }
#iconmenu > button { #iconmenu > button {
pointer-events:all; pointer-events:all;
@ -84,9 +85,8 @@ AFRAME.registerComponent('launcher', {
padding: 5px; padding: 5px;
border-radius: 0px; border-radius: 0px;
} }
#iconmenu > button > img:hover, #iconmenu > button > img:hover{
#iconmenu > button.enable > img { background: #AAA;
border:2px solid #444;
transition:0.gg3s; transition:0.gg3s;
border-radius: 50%; border-radius: 50%;
}` }`
@ -110,24 +110,21 @@ AFRAME.registerComponent('launcher', {
clearTimeout(this.timeout) clearTimeout(this.timeout)
this.timeout = setTimeout( () => { this.timeout = setTimeout( () => {
AFRAME.app.foreach( (app) => { AFRAME.app.foreach( (app) => {
console.dir(app)
if( !app.manifest ) return if( !app.manifest ) return
console.log("--rendering button") console.log(app.data.uri+" "+app.data.order)
return
let btn = app.btn = document.createElement('button') let btn = app.btn = document.createElement('button')
if( app.manifest.icons?.length > 0){ if( app.manifest.icons?.length > 0){
let img = document.createElement('img') let img = document.createElement('img')
img.src = app.manifest.icons[0].src img.src = app.manifest.icons[0].src
img.alt = app.manifest.name img.title = app.manifest.name + ": " + app.manifest.description
btn.appendChild(img) btn.appendChild(img)
}else btn.innerText = app.manifest.short_name }else btn.innerText = app.manifest.short_name
btn.addEventListener('click', () => { btn.addEventListener('click', () => app.el.emit('launcher',app) )
app.el.emit('launcher',app)
})
this.el.dom.querySelector('#iconmenu').appendChild(btn) this.el.dom.querySelector('#iconmenu').appendChild(btn)
}) })
},200) },100)
}, },
manifest: { // HTML5 manifest to identify app to xrsh manifest: { // HTML5 manifest to identify app to xrsh

View File

@ -9,6 +9,17 @@ AFRAME.registerComponent('spatialize', {
document.querySelector('a-scene').addEventListener('exit-vr', () => this.toggle(false) ) document.querySelector('a-scene').addEventListener('exit-vr', () => this.toggle(false) )
// toggle immersive with ESCAPE // toggle immersive with ESCAPE
document.body.addEventListener('keydown', (e) => e.key == 'Escape' && this.toggle() ) document.body.addEventListener('keydown', (e) => e.key == 'Escape' && this.toggle() )
document.head.innerHTML += `<style type="text/css">
.XR #toggle_overlay{
background: transparent;
color: #3aacff;
}
.XR #overlay{
visibility: hidden;
}
</style>`
}, },
requires:{ requires:{
@ -31,7 +42,8 @@ AFRAME.registerComponent('spatialize', {
state = state || !document.body.className.match(/XR/) state = state || !document.body.className.match(/XR/)
document.body.classList[ state ? 'add' : 'remove'](['XR']) document.body.classList[ state ? 'add' : 'remove'](['XR'])
AFRAME.scenes[0].emit( state ? 'apps:XR' : 'apps:2D') AFRAME.scenes[0].emit( state ? 'apps:XR' : 'apps:2D')
this.btn.classList.toggle('enable') this.btn.querySelector('img').src = state ? this.manifest.icons[0].src_2D
: this.manifest.icons[0].src
}, },
manifest: { // HTML5 manifest to identify app to xrsh manifest: { // HTML5 manifest to identify app to xrsh
@ -39,7 +51,8 @@ AFRAME.registerComponent('spatialize', {
"name": "spatialize", "name": "spatialize",
"icons": [ "icons": [
{ {
"src": "https://css.gg/stack.svg", "src": "https://css.gg/display-grid.svg",
"src_2D": "https://css.gg/stack.svg",
"type": "image/svg+xml", "type": "image/svg+xml",
"sizes": "512x512" "sizes": "512x512"
} }
@ -66,7 +79,7 @@ AFRAME.registerComponent('spatialize', {
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }] "icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
} }
], ],
"description": "Hello world information", "description": "use ESC-key to toggle between 2D / 3D",
"screenshots": [ "screenshots": [
{ {
"src": "/images/screenshot1.png", "src": "/images/screenshot1.png",

View File

@ -3,20 +3,37 @@
AFRAME.required = {} AFRAME.required = {}
AFRAME.app = new Proxy({ AFRAME.app = new Proxy({
order:0,
add(component, entity){ add(component, entity){
// categorize by component to prevent similar apps loading duplicate dependencies simultaniously
this[component] = this[component] || [] this[component] = this[component] || []
this[component].push(entity) this[component].push(entity)
entity.data.order = entity.data.order || this.count()
},
count(){
let n = 0
this.foreach( () => n++ )
return n
}, },
foreach(cb){ foreach(cb){
const isArray = (e) => e.push
let arr = []
for( let i in this ){ for( let i in this ){
if( typeof this[i] != 'function' ) this[i].map( (app) => cb({app,component:i}) ) if( isArray(this[i]) ) this[i].map( (app) => arr.push(app.el.app) )
} }
arr.sort( (a,b) => a.data.order > b.data.order )
.map( cb )
} }
},{ },{
get(me,k) { return me[k] }, get(me,k) { return me[k] },
set(me,k,v){ me[k] = v } set(me,k,v){ me[k] = v }
}) })
/*
* This is the abstract 'app' component
*/
AFRAME.registerComponent('app', { AFRAME.registerComponent('app', {
schema:{ schema:{
"uri":{ type:"string"} "uri":{ type:"string"}
@ -57,7 +74,6 @@ AFRAME.registerComponent('app', {
type: String(uri).split(".").pop() // 'mycom.js' => 'js' type: String(uri).split(".").pop() // 'mycom.js' => 'js'
} }
}, },
// usage: require(["./app/foo.js"]) // usage: require(["./app/foo.js"])
// require({foo: "https://foo.com/foo.js"}) // require({foo: "https://foo.com/foo.js"})
require: AFRAME.AComponent.prototype.require = function(packages,readyEvent){ require: AFRAME.AComponent.prototype.require = function(packages,readyEvent){
@ -101,13 +117,17 @@ AFRAME.registerComponent('app', {
}) })
// monkeypatching initComponent will trigger events when components /*
// are initialized (that way apps can react to attached components) * Here are monkeypatched AFRAME component prototype functions
// basically, in both situations: *
// <a-entity foo="a:1"/> * monkeypatching initComponent will trigger events when components
// <a-entity app="uri: myapp.js"/> <!-- myapp.js calls this.require(['foo.js']) --> * 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' * <a-entity foo="a:1"/>
* <a-entity app="uri: myapp.js"/> <!-- myapp.js calls this.require(['foo.js']) -->
*
* event 'foo' will be triggered as both entities (in)directly require component 'foo'
*/
AFRAME.AComponent.prototype.initComponent = function(initComponent){ AFRAME.AComponent.prototype.initComponent = function(initComponent){
return function(){ return function(){
@ -122,7 +142,7 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
return function(){ return function(){
updateProperties.apply(this,arguments) updateProperties.apply(this,arguments)
if( !this.data || !this.data.uri ) return // only deal with apps if( !this.data || !this.data.uri || this.isApp ) return // only deal with apps (once)
// ensure overlay // ensure overlay
let overlay = document.querySelector('#overlay') let overlay = document.querySelector('#overlay')
@ -131,22 +151,22 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
overlay.id = "overlay" overlay.id = "overlay"
document.body.appendChild(overlay) document.body.appendChild(overlay)
document.querySelector("a-scene").setAttribute("webxr","overlayElement:#overlay") document.querySelector("a-scene").setAttribute("webxr","overlayElement:#overlay")
let menu = document.createElement('div') // let menu = document.createElement('div')
menu.id = 'iconmenu' // menu.id = 'iconmenu'
document.body.appendChild(menu) // document.body.appendChild(menu)
} }
// add menu button // // add menu button
if( this.manifest && this.manifest.icons ){ // if( this.manifest && this.manifest.icons ){
let btn = document.createElement('button') // let btn = document.createElement('button')
btn.app = this // btn.app = this
if( this.manifest.icons.length ){ // if( this.manifest.icons.length ){
btn.innerHTML = `<img src="${this.manifest.icons[0].src}"/>` // btn.innerHTML = `<img src="${this.manifest.icons[0].src}"/>`
}else btn.innerText = this.manifest.short_name // }else btn.innerText = this.manifest.short_name
btn.setAttribute("alt", this.manifest.name ) // btn.setAttribute("alt", this.manifest.name )
btn.addEventListener('click', (e) => this.el.emit('launcher',{}) ) // btn.addEventListener('click', (e) => this.el.emit('launcher',{}) )
document.querySelector("#iconmenu").appendChild(btn) // document.querySelector("#iconmenu").appendChild(btn)
} // }
// reactify components with dom-definition // reactify components with dom-definition
if( this.data.uri && this.dom && !this.el.dom ){ if( this.data.uri && this.dom && !this.el.dom ){
@ -220,9 +240,12 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
// assign unique app id // assign unique app id
if( !this.el.uid ) this.el.uid = '_'+String(Math.random()).substr(10) if( !this.el.uid ) this.el.uid = '_'+String(Math.random()).substr(10)
// fetch requires
if( this.requires ) this.require( this.requires, 'requires:ready' ) if( this.requires ) this.require( this.requires, 'requires:ready' )
else this.el.emit('requires:ready') else this.el.emit('requires:ready')
// mark app as being initialized
this.isApp = true
this.el.app = this
} }
}( AFRAME.AComponent.prototype.updateProperties) }( AFRAME.AComponent.prototype.updateProperties)
@ -249,62 +272,5 @@ document.head.innerHTML += `
#overlay.hide{ #overlay.hide{
z-index:-10; z-index:-10;
} }
#toggle_overlay{
display:none;
position: fixed;
right: 20px;
bottom: 73px;
width: 58px;
text-align: center;
height: 40px;
padding: 0;
z-index: 100;
border: 3px solid #3aacff;
border-radius:11px;
transition:0.3s;
padding: 0px;
font-weight: bold;
cursor:pointer;
font-family:sans-serif;
font-size:15px;
color: #FFF;
background: #3aacff;
transition:0.5s;
}
.XR #toggle_overlay{
background: transparent;
color: #3aacff;
}
.XR #overlay{
visibility: hidden;
}
</style> </style>
` `
// draw a button so we can toggle apps between 2D / XR
let toggle = (state) => {
state = state || !document.body.className.match(/XR/)
document.body.classList[ state ? 'add' : 'remove'](['XR'])
AFRAME.scenes[0].emit( state ? 'apps:XR' : 'apps:2D')
}
document.addEventListener("DOMContentLoaded", (event) => {
let btn = document.createElement("button")
btn.id = "toggle_overlay"
btn.innerHTML = "<i class='gg-stack'></i>"
btn.addEventListener('click', (e) => toggle() )
document.body.appendChild(btn)
document.querySelector('a-scene').addEventListener('enter-vr',() => toggle(true) )
document.querySelector('a-scene').addEventListener('exit-vr', () => toggle(false) )
document.querySelector('a-scene').addEventListener('loaded', () => {
let VRbtn = document.querySelector('a-scene .a-enter-vr')
let ARbtn = document.querySelector('a-scene .a-enter-ar')
if( VRbtn ) document.body.appendChild(VRbtn) // move to body
if( ARbtn ) document.body.appendChild(ARbtn) // so they will always be visible
})
// toggle immersive with ESCAPE
document.body.addEventListener('keydown', (e) => e.key == 'Escape' && toggle() )
})