diff --git a/com/add.js b/com/add.js index 16bd6c2..f1109c7 100644 --- a/com/add.js +++ b/com/add.js @@ -15,6 +15,8 @@ AFRAME.registerComponent('add', { events:{ launcher: function(){ + let launcher = this.el.sceneEl.querySelector('[launcher]').components['launcher'] + if( this.el.sceneEl.renderer.xr.isPresenting ){ this.el.sceneEl.exitVR() // *FIXME* we need a gui } diff --git a/com/aframestats.js b/com/aframestats.js new file mode 100644 index 0000000..c391bf2 --- /dev/null +++ b/com/aframestats.js @@ -0,0 +1,77 @@ +AFRAME.registerComponent('aframestats', { + schema: { + foo: { type:"string"} + }, + + init: function () { + this.el.object3D.visible = false + //this.el.innerHTML = ` ` + }, + + events:{ + + launcher: function(e){ + console.dir(this) + let scene = this.el.sceneEl + if( !scene.getAttribute('stats') ){ + scene.setAttribute('stats','') + }else{ + scene.removeAttribute('stats') + } + }, + + }, + + manifest: { // HTML5 manifest to identify app to xrsh + "short_name": "Stats", + "name": "Stats", + "icons": [ + { + "src": "https://css.gg/align-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": "Display FPS / Geometry stats for AFRAME/THREE", + "screenshots": [ + { + "src": "/images/screenshot1.png", + "type": "image/png", + "sizes": "540x720", + "form_factor": "narrow" + } + ], + "help":` +Stats + +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/example/helloworld-htmlform.js b/com/example/helloworld-htmlform.js index f05830e..7bbda72 100644 --- a/com/example/helloworld-htmlform.js +++ b/com/example/helloworld-htmlform.js @@ -14,7 +14,7 @@ AFRAME.registerComponent('helloworld-htmlform', { dom: { scale: 1, events: ['click','input'], - html: (me) => `
+ html: (me) => `
Colour
@@ -25,13 +25,13 @@ AFRAME.registerComponent('helloworld-htmlform', {
- Size: + Size
- +
`, - css: (me) => `.helloworld-htmlform > div { padding:11px; }` + css: (me) => `.htmlform { padding:11px; }` }, @@ -50,22 +50,47 @@ AFRAME.registerComponent('helloworld-htmlform', { // reactive events for this.data updates myvalue: function(e){ this.el.dom.querySelector('#myvalue').innerText = this.data.myvalue }, - 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; } - }); + launcher: async function(){ + let s = await AFRAME.utils.require(this.requires) + + // instance this component + const instance = this.el.cloneNode(false) + this.el.sceneEl.appendChild( instance ) + instance.setAttribute("dom", "") + instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button + instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' ) + instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) ) + instance.setAttribute("grabbable","") + instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera + + const setupWindow = () => { + const com = instance.components['helloworld-htmlform'] + instance.dom.style.display = 'none' + new WinBox("Hello World",{ + width: 250, + height: 340, + x:"center", + y:"center", + id: instance.uid, // important hint for html-mesh + root: document.querySelector("#overlay"), + mount: instance.dom, + onclose: () => { instance.dom.style.display = 'none'; return false; }, + oncreate: () => instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`) + }); + instance.dom.style.display = AFRAME.utils.XD() == '3D' ? 'none' : '' // show/hide + + // hint grabbable's obb-collider to track the window-object + instance.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0' + instance.components['obb-collider'].update() + + // data2event demo + instance.setAttribute("data2event","") + com.data.myvalue = 1 + com.data.foo = `instance ${instance.uid}: ` + setInterval( () => com.data.myvalue++, 500 ) + } + + setTimeout( () => setupWindow(), 10 ) // give new components time to init }, ready: function( ){ @@ -77,8 +102,8 @@ AFRAME.registerComponent('helloworld-htmlform', { }, manifest: { // HTML5 manifest to identify app to xrsh - "short_name": "Hello world", - "name": "Hello world", + "short_name": "Hello world htmlform", + "name": "Hello world htmlform", "icons": [ { "src": "https://css.gg/browser.svg", @@ -108,7 +133,7 @@ AFRAME.registerComponent('helloworld-htmlform', { "icons": [{ "src": "/images/today.png", "sizes": "192x192" }] } ], - "description": "Hello world information", + "description": "Hello world htmlform", "screenshots": [ { "src": "/images/screenshot1.png", diff --git a/com/example/helloworld-window.js b/com/example/helloworld-window.js index 1cba0fd..7e88cd7 100644 --- a/com/example/helloworld-window.js +++ b/com/example/helloworld-window.js @@ -68,10 +68,10 @@ AFRAME.registerComponent('helloworld-window', { instance.components['obb-collider'].update() // data2event demo - //instance.setAttribute("data2event","") - //com.data.myvalue = 1 - //com.data.foo = `instance ${instance.uid}: ` - //setInterval( () => com.data.myvalue++, 500 ) + instance.setAttribute("data2event","") + com.data.myvalue = 1 + com.data.foo = `instance ${instance.uid}: ` + setInterval( () => com.data.myvalue++, 500 ) } setTimeout( () => setupWindow(), 10 ) // give new components time to init diff --git a/com/isoterminal.js b/com/isoterminal.js new file mode 100644 index 0000000..38b0d52 --- /dev/null +++ b/com/isoterminal.js @@ -0,0 +1,211 @@ +AFRAME.registerComponent('isoterminal', { + schema: { + cols: { type: 'number', default: 120 }, + rows: { type: 'number', default: 50 }, + transparent: { type:'boolean', default:false } // need good gpu + }, + + init: function(){ + this.el.object3D.visible = false + }, + + 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", // + xtermcss: "https://unpkg.com/xterm@3.12.0/dist/xterm.css", + xtermjs: "https://unpkg.com/xterm@3.12.0/dist/xterm.js", + axterm: "https://unpkg.com/aframe-xterm-component/aframe-xterm-component.js" + }, + + dom: { + scale: 3, + events: ['click','keydown'], + html: (me) => `
`, + + css: (me) => `.isoterminal{ + overflow:hidden; + }` + }, + + createTerminal: async function(dom){ + let s = await AFRAME.utils.require(this.requires) + //this.el.object3D.visible = true + + const term = this.term = new Terminal({ + allowTransparency: this.data.transparent, + cursorBlink: true, + disableStdin: false, + rows: this.data.rows, + cols: this.data.cols, + fontSize: 16 + }) + debugger + + term.open(dom) + this.canvas = dom.querySelector('.xterm-text-layer') + this.canvas.id = 'terminal-' + (terminalInstance++) + this.canvasContext = this.canvas.getContext('2d') + + this.cursorCanvas = dom.querySelector('.xterm-cursor-layer') + + //this.el.setAttribute('material', `transparent: ${this.data.transparent?'true':'false'}; src: #${this.canvas.id}` ) + + term.on('refresh', () => { + console.log("refresh") + }) + + term.on('data', (data) => { + this.el.emit('xterm-data', data) + }) + + this.el.addEventListener('click', () => { + term.focus() + }) + + const message = 'Hello from \x1B[1;3;31mWebVR\x1B[0m !\r\n$ ' + term.write(message) + }, + + 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: async function(){ + let s = await AFRAME.utils.require(this.requires) + // instance this component + const instance = this.el.cloneNode(false) + this.el.sceneEl.appendChild( instance ) + instance.setAttribute("dom", "") + instance.setAttribute("xd", "") // allows flipping between DOM/WebGL when toggling XD-button + instance.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' ) + instance.setAttribute("position", AFRAME.utils.XD.getPositionInFrontOfCamera(0.5) ) + instance.setAttribute("grabbable","") + instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera + + const setupWindow = () => { + this.createTerminal(instance.dom) + const com = instance.components['isoterminal'] + instance.dom.style.display = 'none' + new WinBox("Hello World",{ + width: window.innerWidth*0.8, + height: window.innerHeight*0.8, + x:"center", + y:"center", + id: instance.uid, // important hint for html-mesh + root: document.querySelector("#overlay"), + mount: instance.dom, + onclose: () => { + if( !confirm('do you want to kill this virtual machine and all its processes?') ) return true + instance.dom.style.display = 'none'; + return false + }, + oncreate: () => instance.setAttribute("html",`html:#${instance.uid}; cursor:#cursor`) + }); + instance.dom.style.display = '' // show + + // hint grabbable's obb-collider to track the window-object + instance.components['obb-collider'].data.trackedObject3D = 'components.html.el.object3D.children.0' + instance.components['obb-collider'].update() + + // data2event demo + //instance.setAttribute("data2event","") + //com.data.myvalue = 1 + //com.data.foo = `instance ${instance.uid}: ` + //setInterval( () => com.data.myvalue++, 500 ) + } + + setTimeout( () => setupWindow(), 10 ) // give new components time to init + + }, + + }, + + 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. + ` + } + +}); + +AFRAME.registerComponent('xterm-shell', { + dependencies: ['xterm'], + init: function() { + const message = 'Run \x1B[1;3;31m\'node server.js\'\x1B[0m to open a shell\r\n' + const xterm = this.el.components['xterm'] + + xterm.write(message) + + const socket = new WebSocket('ws://localhost:8080/') + + // Listen on data, write it to the terminal + socket.onmessage = ({data}) => { + xterm.write(data) + } + + socket.onclose = () => { + xterm.write('\r\nConnection closed.\r\n') + } + + // Listen on user input, send it to the connection + this.el.addEventListener('xterm-data', ({detail}) => { + socket.send(detail) + }) + } +}) diff --git a/com/launcher.js b/com/launcher.js index 9a52c25..b0d9d6a 100644 --- a/com/launcher.js +++ b/com/launcher.js @@ -157,10 +157,9 @@ AFRAME.registerComponent('launcher', { return aentity }, - render: async function(){ + render: async function(els){ if( !this.el.dom ) return // too early (dom.js component not ready) - let items = [...this.el.children] let requires = [] let i = 0 let j = 0 @@ -197,7 +196,8 @@ AFRAME.registerComponent('launcher', { // finally render them! this.el.dom.innerHTML = '' // clear - this.system.components.map( (c) => { + els = els || this.system.components + els.map( (c) => { const launchComponentKey = c.getAttributeNames().shift() const launchCom = c.components[ launchComponentKey ] if( !launchCom ) return console.warn(`could not find component '${launchComponentKey}' (forgot to include script-tag?)`) @@ -216,8 +216,8 @@ AFRAME.registerComponent('launcher', { if( e.detail.withEl.computedMixinStr == 'menuitem' ) return // dont react to menuitems touching eachother // if user press menu button toggle menu - if( launcher && e.srcElement.computedMixinStr == 'menubutton' ){ - return launcher.data.open = !launcher.data.open + if( launcher && !launcher.data.open && e.srcElement.computedMixinStr == 'menubutton' ){ + return (launcher.data.open = true) } if( launcher && !launcher.data.open ) return // dont process menuitems when menu is closed let el = e.srcElement