diff --git a/app/helloworld-html.js b/app/helloworld-html.js index f9cfb0b..87217ea 100644 --- a/app/helloworld-html.js +++ b/app/helloworld-html.js @@ -5,6 +5,7 @@ AFRAME.registerComponent('helloworld-html', { init: function () { + this.el.addEventListener('ready', () => this.el.dom.style.display = 'none' ) }, requires:{ @@ -19,17 +20,12 @@ AFRAME.registerComponent('helloworld-html', { `, css: `.helloworld-html { - color: var(--xrsh-dark-gray); /* see index.css */ - } - `, + color: var(--xrsh-light-gray); /* see index.css */ + }`, }, events:{ - // component events - html: function( ){ console.log("html-mesh requirement mounted") }, - ready: function(e){ console.log("requires are loaded") }, - // combined AFRAME+DOM reactive events keydown: function(e){ }, // click: function(e){ @@ -40,12 +36,18 @@ AFRAME.registerComponent('helloworld-html', { myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue }, - DOMready: function( ){ + ready: function( ){ + this.el.dom.style.display = 'none' console.log("this.el.dom has been added to DOM") this.el.dom.children[0].id = this.el.uid // important hint for html-mesh this.data.myvalue = 1 setInterval( () => this.data.myvalue++, 100 ) - } + }, + + launcher: function(){ + this.el.dom.style.display = '' + console.log("launcher") + }, }, @@ -54,7 +56,7 @@ AFRAME.registerComponent('helloworld-html', { "name": "Hello world", "icons": [ { - "src": "/images/icons-vector.svg", + "src": "https://css.gg/browser.svg", "type": "image/svg+xml", "sizes": "512x512" } diff --git a/app/helloworld-htmlform.js b/app/helloworld-htmlform.js index de86fdd..762ad6a 100644 --- a/app/helloworld-htmlform.js +++ b/app/helloworld-htmlform.js @@ -3,15 +3,12 @@ AFRAME.registerComponent('helloworld-htmlform', { foo: { type:"string"} }, - init: function () { - - }, + init: function () {}, requires:{ html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // deadsimple windows: https://nextapps-de.github.io/winbox - stylis: "https://unpkg.com/stylis@4.3.1/dist/umd/stylis.js", // modern CSS (https://stylis.js.org) }, dom: { @@ -33,19 +30,15 @@ AFRAME.registerComponent('helloworld-htmlform', { `, - css: `.helloworld-htmlform { - div { - padding:11px; - } - } - `, + + css: `.helloworld-htmlform > div { padding:11px; }` + }, events:{ // component events html: function( ){ console.log("html-mesh requirement mounted") }, - stylis: function( ){ console.log("stylis requirement mounted") }, // combined AFRAME+DOM reactive events click: function(e){ }, // @@ -57,27 +50,26 @@ AFRAME.registerComponent('helloworld-htmlform', { // reactive events for this.data updates myvalue: function(e){ this.el.dom.querySelector('#myvalue').innerText = this.data.myvalue }, - // requires are loaded - ready: function(e){ - setTimeout( () => { - new WinBox("Hello World",{ - width: 250, - height: 315, - minwidth:250, - maxwidth:250, - maxheight:315, - minheight:315, - x: 100, - y: 100, - id: this.el.uid, // important hint for html-mesh - root: document.querySelector("#overlay"), - mount: this.el.dom - }); - },500) /*FIXME*/ - + launcher: function(){ + this.el.dom.style.display = '' + new WinBox("Hello World",{ + width: 250, + height: 315, + minwidth:250, + maxwidth:250, + maxheight:315, + minheight:315, + x: 100, + y: 100, + id: this.el.uid, // important hint for html-mesh + root: document.querySelector("#overlay"), + mount: this.el.dom, + onclose: () => { this.el.dom.style.display = 'none'; return false; } + }); }, - DOMready: function( ){ + ready: function( ){ + this.el.dom.style.display = 'none' console.log("this.el.dom has been added to DOM") this.data.myvalue = 1 } @@ -89,7 +81,7 @@ AFRAME.registerComponent('helloworld-htmlform', { "name": "Hello world", "icons": [ { - "src": "/images/icons-vector.svg", + "src": "https://css.gg/browser.svg", "type": "image/svg+xml", "sizes": "512x512" } diff --git a/app/helloworld-window.js b/app/helloworld-window.js index 2ffda16..190653f 100644 --- a/app/helloworld-window.js +++ b/app/helloworld-window.js @@ -3,15 +3,12 @@ AFRAME.registerComponent('helloworld-window', { foo: { type:"string"} }, - init: function () { - - }, + init: function(){}, requires:{ html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox - winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // deadsimple windows: https://nextapps-de.github.io/winbox - stylis: "https://unpkg.com/stylis@4.3.1/dist/umd/stylis.js", // modern CSS (https://stylis.js.org) + winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // }, dom: { @@ -20,12 +17,8 @@ AFRAME.registerComponent('helloworld-window', { html: (me) => `
${me.data.foo} ${me.data.myvalue}
`, - css: `.helloworld-window { - div { - .pad { padding:11px; } - } - } - `, + + css: `.helloworld-window div.pad { padding:11px; }` }, events:{ @@ -41,28 +34,27 @@ AFRAME.registerComponent('helloworld-window', { // reactive events for this.data updates myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue }, - // requires are loaded - ready: function(e){ - - setTimeout( () => { - new WinBox("Hello World",{ - width: 250, - height: 150, - x:"center", - y:"center", - id: this.el.uid, // important hint for html-mesh - root: document.querySelector("#overlay"), - mount: this.el.dom - }); - }, 500 ) - - }, - - DOMready: function( ){ + ready: function( ){ + this.el.dom.style.display = 'none' console.log("this.el.dom has been added to DOM") this.data.myvalue = 1 setInterval( () => this.data.myvalue++, 100 ) - } + }, + + launcher: function(){ + console.log("this.el.dom has been added to DOM") + new WinBox("Hello World",{ + width: 250, + height: 150, + x:"center", + y:"center", + id: this.el.uid, // important hint for html-mesh + root: document.querySelector("#overlay"), + mount: this.el.dom, + onclose: () => { this.el.dom.style.display = 'none'; return false; } + }); + this.el.dom.style.display = '' + }, }, @@ -71,7 +63,7 @@ AFRAME.registerComponent('helloworld-window', { "name": "Hello world", "icons": [ { - "src": "/images/icons-vector.svg", + "src": "https://css.gg/browser.svg", "type": "image/svg+xml", "sizes": "512x512" } diff --git a/app/helloworld.js b/app/helloworld.js index ec96a47..bbd7863 100644 --- a/app/helloworld.js +++ b/app/helloworld.js @@ -3,8 +3,12 @@ AFRAME.registerComponent('helloworld', { foo: { type:"string"} }, - init: function () { + init: function () { + this.el.object3D.visible = false this.el.setAttribute("geometry","primitive: octahedron") + this.interval = setInterval( () => { + this.data.myvalue = ((this.data.myvalue||1.0) + 0.25) % 1 + }, 400 ) }, requires:{ @@ -17,6 +21,13 @@ AFRAME.registerComponent('helloworld', { somecomponent: function( ){ console.log("component requirement mounted") }, ready: function(e){ console.log("requires are loaded") }, + launcher: function(e){ + this.el.object3D.visible = !this.el.object3D.visible + }, + + // reactive this.data value demo + myvalue:function( ){ this.el.object3D.children[0].scale.y = this.data.myvalue } + }, manifest: { // HTML5 manifest to identify app to xrsh @@ -24,7 +35,7 @@ AFRAME.registerComponent('helloworld', { "name": "Hello world", "icons": [ { - "src": "/images/icons-vector.svg", + "src": "https://css.gg/shape-hexagon.svg", "type": "image/svg+xml", "sizes": "512x512" } diff --git a/app/isoterminal.js b/app/isoterminal.js new file mode 100644 index 0000000..f2132aa --- /dev/null +++ b/app/isoterminal.js @@ -0,0 +1,110 @@ +AFRAME.registerComponent('isoterminal', { + schema: { + foo: { type:"string"} + }, + + init: function(){}, + + requires:{ + html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME + winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox + winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // + }, + + dom: { + scale: 3, + events: ['click','keydown'], + html: (me) => `
+
to be implemented +
`, + + css: `.helloworld-window div.pad { padding:11px; }` + }, + + events:{ + + // combined AFRAME+DOM reactive events + click: function(e){ }, // + keydown: function(e){ }, // + + // reactive events for this.data updates + myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue }, + + ready: function( ){ + this.el.dom.style.display = 'none' + }, + + launcher: function(){ + new WinBox( this.manifest.iso + ' ' + this.manifest.name, { + width: '70%', + height: '80%', + x:"center", + y:"center", + id: this.el.uid, // important hint for html-mesh + root: document.querySelector("#overlay"), + mount: this.el.dom, + onclose: () => { + if( !confirm('do you want to kill this virtual machine and all its processes?') ) return true + this.el.dom.style.display = 'none' + return false + } + }); + this.el.dom.style.display = '' + }, + + }, + + manifest: { // HTML5 manifest to identify app to xrsh + "iso": "linux-x64-4.15.iso", + "short_name": "ISOTerm", + "name": "terminal", + "icons": [ + { + "src": "https://css.gg/terminal.svg", + "type": "image/svg+xml", + "sizes": "512x512" + } + ], + "id": "/?source=pwa", + "start_url": "/?source=pwa", + "background_color": "#3367D6", + "display": "standalone", + "scope": "/", + "theme_color": "#3367D6", + "shortcuts": [ + { + "name": "What is the latest news?", + "cli":{ + "usage": "helloworld [options]", + "example": "helloworld news", + "args":{ + "--latest": {type:"string"} + } + }, + "short_name": "Today", + "description": "View weather information for today", + "url": "/today?source=pwa", + "icons": [{ "src": "/images/today.png", "sizes": "192x192" }] + } + ], + "description": "Hello world information", + "screenshots": [ + { + "src": "/images/screenshot1.png", + "type": "image/png", + "sizes": "540x720", + "form_factor": "narrow" + } + ], + "help":` +Helloworld application + +This is a help file which describes the application. +It will be rendered thru troika text, and will contain +headers based on non-punctualized lines separated by linebreaks, +in above's case "\nHelloworld application\n" will qualify as header. + ` + } + +}); + diff --git a/app/launcher.js b/app/launcher.js new file mode 100644 index 0000000..dcae6d2 --- /dev/null +++ b/app/launcher.js @@ -0,0 +1,185 @@ +/* + * ## launcher + * + * displays app (icons) for enduser to launch + * + * ```javascript + * + * ``` + * + * | property | type | example | + * |--------------|--------------------|----------------------------------------------------------------------------------------| + * | `registries` | `array` of strings | | + * + * | event | target | info | + * |--------------|-------------------------------------------------------------------------------------------------------------| + * | `launcher` | an app | when pressing an app icon, `launcher` event will be send to the respective app | + */ + +AFRAME.registerComponent('launcher', { + schema: { + foo: { type:"string"} + }, + + init: function () { + this.data.apps = [] + + AFRAME.scenes.map( (scene) => { + scene.addEventListener('app:ready', (e) => this.render(e.detail) ) + }) + }, + + requires:{ + html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME + }, + + dom: { + scale: 3, + events: ['click'], + html: (me) => `
+
+
`, + + css: `#iconmenu { + z-index: 1000; + display: flex; + flex-direction: column-reverse; + align-items: flex-end; + height: 100%; + position: fixed; + top: 0px; + right: 20px; + bottom: 0; + padding-bottom: 72px; + box-sizing: border-box; + pointer-events: none; + } + #iconmenu > button { + pointer-events:all; + width: 58px; + height: 39px; + padding: 12px 0px 20px 0px; + border-radius: 0px; + color: var(--xrsh-primary); + background: #FFF; + border-left: 5px solid var(--xrsh-primary); + border-right: 5px solid var(--xrsh-primary); + } + + #iconmenu > button:first-child { + border-radius:0px 0px 30px 30px; + border-bottom: 5px solid var(--xrsh-primary); + padding-bottom:35px; + } + + #iconmenu > button:last-child { + border-radius:30px 30px 0px 0px; + border-top: 5px solid var(--xrsh-primary); + padding-top:13px; + } + + #iconmenu > button > img { + transform: translate(0px,-5px); + opacity:0.5; + padding: 5px; + border-radius: 0px; + } + #iconmenu > button > img:hover, + #iconmenu > button.enable > img { + border:2px solid #444; + transition:0.gg3s; + border-radius: 50%; + }` + }, + + events:{ + + // combined AFRAME+DOM reactive events + click: function(e){ + console.dir(e) + }, // + + + ready: function( ){ + this.el.dom.children[0].id = this.el.uid // important hint for html-mesh + }, + + }, + + render: function(app){ + clearTimeout(this.timeout) + this.timeout = setTimeout( () => { + AFRAME.app.foreach( (app) => { + console.dir(app) + if( !app.manifest ) return + + console.log("--rendering button") + return + let btn = app.btn = document.createElement('button') + if( app.manifest.icons?.length > 0){ + let img = document.createElement('img') + img.src = app.manifest.icons[0].src + img.alt = app.manifest.name + btn.appendChild(img) + }else btn.innerText = app.manifest.short_name + btn.addEventListener('click', () => { + app.el.emit('launcher',app) + }) + this.el.dom.querySelector('#iconmenu').appendChild(btn) + }) + },200) + }, + + manifest: { // HTML5 manifest to identify app to xrsh + "short_name": "Hello world", + "name": "Hello world", + "icons": [ + { + "src": "https://css.gg/browser.svg", + "type": "image/svg+xml", + "sizes": "512x512" + } + ], + "id": "/?source=pwa", + "start_url": "/?source=pwa", + "background_color": "#3367D6", + "display": "standalone", + "scope": "/", + "theme_color": "#3367D6", + "shortcuts": [ + { + "name": "What is the latest news?", + "cli":{ + "usage": "helloworld [options]", + "example": "helloworld news", + "args":{ + "--latest": {type:"string"} + } + }, + "short_name": "Today", + "description": "View weather information for today", + "url": "/today?source=pwa", + "icons": [{ "src": "/images/today.png", "sizes": "192x192" }] + } + ], + "description": "Hello world information", + "screenshots": [ + { + "src": "/images/screenshot1.png", + "type": "image/png", + "sizes": "540x720", + "form_factor": "narrow" + } + ], + "help":` +Helloworld application + +This is a help file which describes the application. +It will be rendered thru troika text, and will contain +headers based on non-punctualized lines separated by linebreaks, +in above's case "\nHelloworld application\n" will qualify as header. + ` + } + +}); + diff --git a/app/manual.js b/app/manual.js index 46f7ef9..38f6ed5 100644 --- a/app/manual.js +++ b/app/manual.js @@ -4,14 +4,14 @@ AFRAME.registerComponent('manual', { }, init: function () { - //AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb") + // requires are loaded + this.el.addEventListener('ready', () => this.el.dom.style.display = 'none' ) }, requires:{ html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", // deadsimple windows: https://nextapps-de.github.io/winbox - xrfragments: "https://xrfragment.org/dist/xrfragment.aframe.js", }, dom: { @@ -35,19 +35,13 @@ AFRAME.registerComponent('manual', {
`, - css: `.manual { - div { - padding:11px; - } - } - `, + css: `.manual > div { padding:11px }` }, events:{ // component events html: function( ){ console.log("html-mesh requirement mounted") }, - stylis: function( ){ console.log("stylis requirement mounted") }, // combined AFRAME+DOM reactive events click: function(e){ }, // @@ -56,26 +50,24 @@ AFRAME.registerComponent('manual', { // reactive events for this.data updates myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue }, - // requires are loaded - ready: function(e){ - - setTimeout( () => { - new WinBox("XRSH manual",{ - width:300, - height:300, - x:"center", - y:"center", - id: this.el.uid, // important hint for html-mesh - root: document.querySelector("#overlay"), - mount: this.el.dom - }); - }, 500 ) - - this.el.setAttribute("xrf","https://coderofsalvation.github.io/xrsh-media/assets/background.glb") + launcher: function(){ + new WinBox( this.manifest.name, { + width: '70%', + height: '70%', + x:"center", + y:"center", + id: this.el.uid, // important hint for html-mesh + root: document.querySelector("#overlay"), + mount: this.el.dom, + onclose: () => { this.el.dom.style.display = 'none'; return false; } + }); + this.el.dom.style.display = '' + this.el.setAttribute("xrf", document.location.search || "https://coderofsalvation.github.io/xrsh-media/assets/background.glb") // navigate with: AFRAME.XRF.navigator.to("https://xrfragment.org/index.glb") }, DOMready: function( ){ + this.el.dom.style.display = 'none' console.log("this.el.dom has been added to DOM") this.data.myvalue = 1 setInterval( () => this.data.myvalue++, 100 ) @@ -84,11 +76,11 @@ AFRAME.registerComponent('manual', { }, manifest: { // HTML5 manifest to identify app to xrsh - "short_name": "Hello world", - "name": "Hello world", + "short_name": "XRSH Manual", + "name": "XRSH Manual", "icons": [ { - "src": "/images/icons-vector.svg", + "src": "https://css.gg/coffee.svg", "type": "image/svg+xml", "sizes": "512x512" } diff --git a/app/spatialize.js b/app/spatialize.js new file mode 100644 index 0000000..a574eae --- /dev/null +++ b/app/spatialize.js @@ -0,0 +1,89 @@ +AFRAME.registerComponent('spatialize', { + schema: { + foo: { type:"string"} + }, + + init: function () { + + document.querySelector('a-scene').addEventListener('enter-vr',() => this.toggle(true) ) + document.querySelector('a-scene').addEventListener('exit-vr', () => this.toggle(false) ) + // toggle immersive with ESCAPE + document.body.addEventListener('keydown', (e) => e.key == 'Escape' && this.toggle() ) + }, + + requires:{ + // somecomponent: "https://unpkg.com/some-aframe-component/mycom.min.js" + }, + + events:{ + + // component events + ready: function(e){ + this.btn.style.background = 'var(--xrsh-primary)' + }, + + launcher: function(e){ this.toggle() }, + + }, + + // draw a button so we can toggle apps between 2D / XR + toggle: function(state){ + state = state || !document.body.className.match(/XR/) + document.body.classList[ state ? 'add' : 'remove'](['XR']) + AFRAME.scenes[0].emit( state ? 'apps:XR' : 'apps:2D') + this.btn.classList.toggle('enable') + }, + + manifest: { // HTML5 manifest to identify app to xrsh + "short_name": "spatialize", + "name": "spatialize", + "icons": [ + { + "src": "https://css.gg/stack.svg", + "type": "image/svg+xml", + "sizes": "512x512" + } + ], + "id": "/?source=pwa", + "start_url": "/?source=pwa", + "background_color": "#3367D6", + "display": "standalone", + "scope": "/", + "theme_color": "#3367D6", + "shortcuts": [ + { + "name": "What is the latest news?", + "cli":{ + "usage": "helloworld [options]", + "example": "helloworld news", + "args":{ + "--latest": {type:"string"} + } + }, + "short_name": "Today", + "description": "View weather information for today", + "url": "/today?source=pwa", + "icons": [{ "src": "/images/today.png", "sizes": "192x192" }] + } + ], + "description": "Hello world information", + "screenshots": [ + { + "src": "/images/screenshot1.png", + "type": "image/png", + "sizes": "540x720", + "form_factor": "narrow" + } + ], + "help":` +Helloworld application + +This is a help file which describes the application. +It will be rendered thru troika text, and will contain +headers based on non-punctualized lines separated by linebreaks, +in above's case "\nHelloworld application\n" will qualify as header. + ` + } + +}); + diff --git a/app/vconsole.js b/app/vconsole.js new file mode 100644 index 0000000..99b96c0 --- /dev/null +++ b/app/vconsole.js @@ -0,0 +1,94 @@ +AFRAME.registerComponent('vconsole', { + schema: { + foo: { type:"string"} + }, + + init: function () { + //AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb") + document.head.innerHTML += ` + + ` + }, + + requires:{ + vconsole: "https://unpkg.com/vconsole@latest/dist/vconsole.min.js" + }, + + events:{ + + // requires are loaded + ready: function(e){ + this.vConsole = new window.VConsole() + document.querySelector('.vc-mask').remove() + document.querySelector('.vc-switch').remove() + }, + + launcher: function(){ + let panel = document.querySelector('.vc-panel') + if( panel.style.display == 'none' ) this.vConsole.show() + else this.vConsole.hide() + }, + }, + + manifest: { // HTML5 manifest to identify app to xrsh + "short_name": "Hello world", + "name": "Hello world", + "icons": [ + { + "src": "https://css.gg/border-bottom.svg", + "type": "image/svg+xml", + "sizes": "512x512" + } + ], + "id": "/?source=pwa", + "start_url": "/?source=pwa", + "background_color": "#3367D6", + "display": "standalone", + "scope": "/", + "theme_color": "#3367D6", + "shortcuts": [ + { + "name": "What is the latest news?", + "cli":{ + "usage": "helloworld [options]", + "example": "helloworld news", + "args":{ + "--latest": {type:"string"} + } + }, + "short_name": "Today", + "description": "View weather information for today", + "url": "/today?source=pwa", + "icons": [{ "src": "/images/today.png", "sizes": "192x192" }] + } + ], + "description": "Hello world information", + "screenshots": [ + { + "src": "/images/screenshot1.png", + "type": "image/png", + "sizes": "540x720", + "form_factor": "narrow" + } + ], + "help":` +Helloworld application + +This is a help file which describes the application. +It will be rendered thru troika text, and will contain +headers based on non-punctualized lines separated by linebreaks, +in above's case "\nHelloworld application\n" will qualify as header. + ` + } + +}); + diff --git a/app/xrfragments.js b/app/xrfragments.js new file mode 100644 index 0000000..be56025 --- /dev/null +++ b/app/xrfragments.js @@ -0,0 +1,73 @@ +AFRAME.registerComponent('xrfragments', { + schema: { + url: { type:"string"} + }, + + init: function () { + }, + + requires:{ + xrfragments: "https://xrfragment.org/dist/xrfragment.aframe.js", + }, + + events:{ + + // requires are loaded + ready: function(e){ + this.el.setAttribute("xrf","https://coderofsalvation.github.io/xrsh-media/assets/background.glb") + }, + + launcher: function(){ + let url = prompt('enter URL to glb/fbx/json/obj/usdz asset', 'https://xrfragment.org/index.glb') + if( url ) AFRAME.XRF.navigator.to(url) + } + + }, + + manifest: { // HTML5 manifest to identify app to xrsh + "short_name": "XRF", + "name": "XR Fragment URL", + "icons": [ ], + "id": "/?source=pwa", + "start_url": "/?source=pwa", + "background_color": "#3367D6", + "display": "standalone", + "scope": "/", + "theme_color": "#3367D6", + "shortcuts": [ + { + "name": "What is the latest news?", + "cli":{ + "usage": "helloworld [options]", + "example": "helloworld news", + "args":{ + "--latest": {type:"string"} + } + }, + "short_name": "Today", + "description": "View weather information for today", + "url": "/today?source=pwa", + "icons": [{ "src": "/images/today.png", "sizes": "192x192" }] + } + ], + "description": "Hello world information", + "screenshots": [ + { + "src": "/images/screenshot1.png", + "type": "image/png", + "sizes": "540x720", + "form_factor": "narrow" + } + ], + "help":` +Helloworld application + +This is a help file which describes the application. +It will be rendered thru troika text, and will contain +headers based on non-punctualized lines separated by linebreaks, +in above's case "\nHelloworld application\n" will qualify as header. + ` + } + +}); + diff --git a/com/app.js b/com/app.js index f6c1a70..d15952d 100644 --- a/com/app.js +++ b/com/app.js @@ -28,7 +28,18 @@ AFRAME.registerComponent('app', { AFRAME.app[component].map( (app) => { if( !app.el.getAttribute(component) ) app.el.setAttribute(component,app.data) }) - } + }, + "requires:ready": function(){ + let {id,component,type} = this.parseAppURI(this.data.uri) + AFRAME.app[component].map( (app) => { + if( app.readyFired ) return + setTimeout( () => { + if( this.el.dom ) this.el.dom.style.display = '' // finally show dom elements + app.el.emit('ready') + app.readyFired = true + },400) // big js scripts need some parsing time + }) + }, }, init: function() { @@ -83,7 +94,9 @@ AFRAME.registerComponent('app', { } }catch(e){ console.error(`package ${package} could not be retrieved..aborting :(`); throw e; } }) - Promise.all(deps).then( () => this.el.emit( readyEvent||'ready', packages) ) + Promise.all(deps).then( () => { + this.el.emit( readyEvent || 'requireReady', packages) + }) } }) @@ -109,39 +122,50 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ return function(){ updateProperties.apply(this,arguments) + if( !this.data || !this.data.uri ) return // only deal with apps - 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}) - } - }) + // ensure overlay + 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") + let menu = document.createElement('div') + menu.id = 'iconmenu' + document.body.appendChild(menu) + } - if( !this.data ) return + // 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) + } // reactify components with dom-definition if( this.data.uri && this.dom && !this.el.dom ){ 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.className = this.parseAppURI(this.data.uri).component this.el.dom.innerHTML = this.dom.html(this) + this.el.dom.style.display = 'none' 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 @@ -159,37 +183,46 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){ return tasks }, - requireDependencies: () => { - this.require( this.requires ) - return tasks - }, - setupListeners: () => { this.scene.addEventListener('apps:2D', () => this.el.setAttribute('visible', false) ) this.scene.addEventListener('apps:XR', () => { - console.log("JAXR") this.el.setAttribute('visible', true) this.el.setAttribute("html",`html:#${this.el.uid}; cursor:#cursor`) }) return tasks + }, + + triggerKeyboardForInputs: () => { + // https://developer.oculus.com/documentation/web/webxr-keyboard ; + [...this.el.dom.querySelectorAll('[type=text]')].map( (input) => { + let triggerKeyboard = function(){ + this.focus() + console.log("focus") + } + input.addEventListener('click', triggerKeyboard ) + }) + return tasks } } tasks - .ensureOverlay() .addCSS() .createReactiveDOMElement() .scaleDOMvsXR() - .requireDependencies() + .triggerKeyboardForInputs() .setupListeners() - tasks.overlay.appendChild(this.el.dom) + document.querySelector('#overlay').appendChild(this.el.dom) this.el.emit('DOMready',{el: this.el.dom}) } // assign unique app id if( !this.el.uid ) this.el.uid = '_'+String(Math.random()).substr(10) + + // fetch requires + if( this.requires ) this.require( this.requires, 'requires:ready' ) + else this.el.emit('requires:ready') } }( AFRAME.AComponent.prototype.updateProperties) @@ -218,6 +251,7 @@ document.head.innerHTML += ` } #toggle_overlay{ + display:none; position: fixed; right: 20px; bottom: 73px;