diff --git a/app/helloworld.js b/app/helloworld.js index cbb2fd8..9f7ad6b 100644 --- a/app/helloworld.js +++ b/app/helloworld.js @@ -1,42 +1,36 @@ AFRAME.registerComponent('helloworld', { schema: { - + foo: { type:"string"} }, + dependencies:{ - "iso": "https://rawgit.com/Utopiah/reponame/master/dist/file.min.js", // fallback URL for xrsh in case component - "xterm": "https://rawgit.com/Utopiah/reponame/master/dist/file.min.js", // was not loaded (in AFRAME.components) - "content-menu": "https://rawgit.com/Utopiah/reponame/master/dist/file.min.js", // TIP: include branch/commit in URL to lock specify version + "html": "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME + "stylis": "https://unpkg.com/stylis@4.3.1/dist/umd/stylis.js" // modern CSS (https://stylis.js.org) }, + + dom: { + scale: 3.5, + events: ['click'], + html: `
`, + css: `div{ #hello {position:relative;top:0;width:300px} }` + }, + events:{ - - "iso": function(tty){// act when component gets mounted - // 'term' is basically AFRAME.components.ISOterminal - tty.write('hello to tty ISO-os from AFRAME') - tty.on('stdout', (data) => { - // react to data being spoken/typed into the terminal - // (spatial prompting like 'open foo.gltf', 'component helloworld' e.g.) - }) - }, - - "xterm": function(xterm){// act when component gets mounted - // 'term' is basically AFRAME.components.ISOterminal - }, - - "content-menu": function(menu){ - menu.add({ - name: 'edit', // "everything must have an edit-button" ~ Fabien Benetou - icon: 'gear', // see https://jsonforms.io to see json-2-html forms - type: 'object', // json-2-webxr has nothing like it (yet) but offers uniform interfaces across components - properties:{ - enabled: { type: 'boolean', default: true, format: 'checkbox' }, - edit_terminal: { type: 'function', cb: () => AFRAME.components.ISOterminal.exec('pico /com/helloworld.js') }, - edit_spatial: { type: 'function', cb: () => this.require({"spatial-edit":{required:true}}) } - } - }) - } + "html": function( ){ console.log("htmlmesh component mounted!") }, // html-component was added to this AFRAME entity + "title": function(e){ this.dom.el.querySelector("button").innerHTML = e.detail.v }, // this.data.title was changed + "click": function(e){ // a click was detected on this.dom.el or AFRAME entity + this.data.title = 'hello world '+(new Date().getTime()) + console.dir(e.detail.target || e.target) + }, }, - init: function () { }, + init: function () { + this.require( this.dependencies ) + .then( () => { + document.body.appendChild(this.dom.el) + this.el.setAttribute("html",'html:#hello; cursor:#cursor') + }) + }, manifest: { // HTML5 manifest to identify app to xrsh "short_name": "Hello world", @@ -56,18 +50,18 @@ AFRAME.registerComponent('helloworld', { "theme_color": "#3367D6", "shortcuts": [ { - "name": "How are you today?", + "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" }] - }, - { - "name": "How's weather tomorrow?", - "short_name": "Tomorrow", - "description": "View weather information for tomorrow", - "url": "/tomorrow?source=pwa", - "icons": [{ "src": "/images/tomorrow.png", "sizes": "192x192" }] } ], "description": "Hello world information", diff --git a/app/iso.js b/app/iso.js new file mode 100644 index 0000000..59f869b --- /dev/null +++ b/app/iso.js @@ -0,0 +1,113 @@ +AFRAME.registerComponent('helloworld', { + schema: { + foo: { type:"string"} + }, + dependencies:{ + "html": "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME + "stylis": "https://unpkg.com/stylis@4.3.1/dist/umd/stylis.js" // modern CSS (https://stylis.js.org) + }, + dom: { + scale: 3.5, + events: ['click'], + html: `
`, + css: `div{ #hello {position:absolute;top:0;width:300px} }` + }, + events:{ + "html": function( ){ console.log("htmlmesh component mounted!") }, // html-component was added to this AFRAME entity + "title": function(e){ this.dom.el.querySelector("button").innerHTML = e.detail.v }, // this.data.title was changed + "click": function(e){ alert("clicked "+ (e.detail.target || e.target).tagName ) }, // a click was detected on this.dom.el or AFRAME entity +// "aframe-html": function(){ +// this.el.setAttribute() +// alert("aframe loaded") +// } + +// "iso": function(tty){// act when component gets mounted +// // 'term' is basically AFRAME.components.ISOterminal +// tty.write('hello to tty ISO-os from AFRAME') +// tty.on('stdout', (data) => { +// // react to data being spoken/typed into the terminal +// // (spatial prompting like 'open foo.gltf', 'component helloworld' e.g.) +// }) +// }, +// +// "xterm": function(xterm){// act when component gets mounted +// // 'term' is basically AFRAME.components.ISOterminal +// }, +// +// "content-menu": function(menu){ +// menu.add({ +// name: 'edit', // "everything must have an edit-button" ~ Fabien Benetou +// icon: 'gear', // see https://jsonforms.io to see json-2-html forms +// type: 'object', // json-2-webxr has nothing like it (yet) but offers uniform interfaces across components +// properties:{ +// enabled: { type: 'boolean', default: true, format: 'checkbox' }, +// edit_terminal: { type: 'function', cb: () => AFRAME.components.ISOterminal.exec('pico /com/helloworld.js') }, +// edit_spatial: { type: 'function', cb: () => this.require({"spatial-edit":{required:true}}) } +// } +// }) +// } + }, + + init: function () { + this.require( this.dependencies ) + .then( () => { + document.body.appendChild(this.dom.el) + + setInterval( () => this.data.title = String(Math.random()), 500 ) + this.el.setAttribute("html",'html:#hello; cursor:#cursor') + }) + }, + + manifest: { // HTML5 manifest to identify app to xrsh + "short_name": "Hello world", + "name": "Hello world", + "icons": [ + { + "src": "/images/icons-vector.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": "How are you today?", + "short_name": "Today", + "description": "View weather information for today", + "url": "/today?source=pwa", + "icons": [{ "src": "/images/today.png", "sizes": "192x192" }] + }, + { + "name": "How's weather tomorrow?", + "short_name": "Tomorrow", + "description": "View weather information for tomorrow", + "url": "/tomorrow?source=pwa", + "icons": [{ "src": "/images/tomorrow.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 new file mode 100644 index 0000000..7efc2d7 --- /dev/null +++ b/com/app.js @@ -0,0 +1,158 @@ +// this is a highlevel way of loading buildless 'apps' (a collection of js components) + +AFRAME.registerComponent('app', { + schema:{ + "uri":{ type:"string"} + }, + init: function() { + this.require([ this.data.uri ]) + .then( () => { + let id = this.data.uri.split("/").pop() // 'a/b/c/mycom.js' => 'mycom.js' + let component = id.split(".js").shift() // 'mycom.js' => 'mycom' + let entity = document.createElement("a-entity") + this.el.setAttribute(component,this.data) + }) + }, + + + // 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){ + let 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}) + } + }) + let el = document.createElement('div') + el.innerHTML = this.dom.html + this.data = reactify( el, this.el ) + this.dom.events.map( (e) => el.addEventListener(e, (ev) => this.el.emit(e,ev) ) ) + this.dom.el = el + // add css if any + if( this.dom.css && !document.head.querySelector(`style#${this.attrName}`) ){ + document.head.innerHTML += `` + } + if( this.dom.scale ) this.el.setAttribute('scale',`${this.dom.scale} ${this.dom.scale} ${this.dom.scale}`) + //('[helloworld]').object3D.children[0].material.map.magFilter = THREE.NearestFilter + } + } +}( AFRAME.AComponent.prototype.updateProperties) + +// +// base CSS for XRSH apps +// +document.head.innerHTML += ` + +` diff --git a/com/require.js b/com/require.js deleted file mode 100644 index 6a01ad1..0000000 --- a/com/require.js +++ /dev/null @@ -1,14 +0,0 @@ -AFRAME.registerComponent('require', { - init: function() { - }, - -}) -// work in progress -// -// // -//const updateComponents = AFRAME.AEntity.prototype.updateComponents -//AFRAME.AEntity.prototype.updateComponents = function(updateComponents){ -// return function(){ -// return updateComponents.apply(this,args) -// } -//}(updateComponents)