+
${me.data.foo} ${me.data.myvalue}
+
`,
+ css: `.helloworld-window {
+ div {
+ .pad { 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){ }, //
+ keydown: function(e){ }, //
+
+ // 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( ){
+ console.log("this.el.dom has been added to DOM")
+ this.data.myvalue = 1
+ setInterval( () => this.data.myvalue++, 100 )
+ }
+
+ },
+
+ 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": "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/helloworld.js b/app/helloworld.js
index 60890a4..ec96a47 100644
--- a/app/helloworld.js
+++ b/app/helloworld.js
@@ -3,76 +3,20 @@ AFRAME.registerComponent('helloworld', {
foo: { type:"string"}
},
- requires:{
- 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)
+ init: function () {
+ this.el.setAttribute("geometry","primitive: octahedron")
},
- dom: {
- scale: 3,
- events: ['click','input'],
- html: (me) => `
- `,
- css: `.modal.hello {
- position:relative;
- top:0;
- width:200px;
- .title { font-weight:bold; } /* modern nested buildless css thanks to stylis */
- }`
+ requires:{
+ // somecomponent: "https://unpkg.com/some-aframe-component/mycom.min.js"
},
events:{
- html: function( ){ console.log("html-mesh requirement mounted") },
- stylis: function( ){ console.log("stylis requirement mounted") },
+ // component events
+ somecomponent: function( ){ console.log("component requirement mounted") },
+ ready: function(e){ console.log("requires are loaded") },
- DOMready: function(e){
- // our reactive dom element has been added to the dom (DOMElement = this.el.dom)
- },
-
- click: function(e){ // a click was detected on this.el.dom or AFRAME entity
- let el = e.detail.target || e.detail.srcElement
- if( !el ) return
- if( el.className.match("fold") ) this.el.toggleFold()
- if( el.className.match("close") ) this.el.close()
- },
-
- input: function(e){
- if( !e.detail.target ) return
- if( e.detail.target.id == 'myRange' ) this.data.value = e.detail.target.value // reactive demonstration
- },
-
- value: function(e){ this.el.dom.querySelector("#value").innerHTML = e.detail.v }, // auto-emitted when this.data.value gets updated
-
- },
-
- init: function () {
- this.require( this.requires )
-
- this.scene.addEventListener('apps:2D', () => this.el.setAttribute('visible', false) )
- this.scene.addEventListener('apps:XR', () => {
- this.el.setAttribute('visible', true)
- this.el.setAttribute("html",`html:#${this.el.uid}; cursor:#cursor`)
- })
},
manifest: { // HTML5 manifest to identify app to xrsh
diff --git a/app/helloworld.old.js b/app/helloworld.old.js
new file mode 100644
index 0000000..db483ed
--- /dev/null
+++ b/app/helloworld.old.js
@@ -0,0 +1,137 @@
+AFRAME.registerComponent('helloworld', {
+ schema: {
+ foo: { type:"string"}
+ },
+
+ requires:{
+ 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,
+ events: ['click','input'],
+ html: (me) => `
+ `,
+ css: `
+ /*
+* * HTML-2-WebGL limitations / guidelines for html-mesh compatibility:
+ * no icon libraries (favicon e.g.)
+ * in case of 'border-radius: 2px 3px 4px 5px' (2px will apply to all corners)
+ * dont use transform: scale(1.2) e.g.
+* */
+ .modal.hello {
+ position:relative;
+ top:0;
+ width:200px;
+ .title { font-weight:bold; } /* modern nested buildless css thanks to stylis */
+ }`
+ },
+
+ events:{
+
+ html: function( ){ console.log("html-mesh requirement mounted") },
+ stylis: function( ){ console.log("stylis requirement mounted") },
+
+ DOMready: function(e){
+ // our reactive dom element has been added to the dom (DOMElement = this.el.dom)
+ },
+
+ click: function(e){ // a click was detected on this.el.dom or AFRAME entity
+ let el = e.detail.target || e.detail.srcElement
+ if( !el ) return
+ if( el.className.match("fold") ) this.el.toggleFold()
+ if( el.className.match("close") ) this.el.close()
+ },
+
+ input: function(e){
+ if( !e.detail.target ) return
+ if( e.detail.target.id == 'myRange' ) this.data.value = e.detail.target.value // reactive demonstration
+ },
+
+ value: function(e){ this.el.dom.querySelector("#value").innerHTML = e.detail.v }, // auto-emitted when this.data.value gets updated
+
+ },
+
+ init: function () {
+ this.require( this.requires )
+
+ this.scene.addEventListener('apps:2D', () => this.el.setAttribute('visible', false) )
+ this.scene.addEventListener('apps:XR', () => {
+ this.el.setAttribute('visible', true)
+ this.el.setAttribute("html",`html:#${this.el.uid}; 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": "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 842db20..e80fdb8 100644
--- a/app/manual.js
+++ b/app/manual.js
@@ -3,53 +3,87 @@ AFRAME.registerComponent('manual', {
foo: { type:"string"}
},
- requires:{},
+ 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: {
- events: ['click'],
- html: (me) => `
-
-
Welcome to XRSHell
-
-
-
- The
xrsh (xrshell) brings the
FOSS- and
Linux-soul to
WebXR, promoting the use of (interactive text) terminal and user-provided operating systems inside WebXR.
-
Technically,
xrsh is a bundle of freshly created re-usable FOSS WebXR components.
These provide a common filesystem interface for interacting with WebXR, offering the well-known linux/unix toolchain including a commandline to invoke, store, edit and run WebXR utilities - regardless of their implementation.
-
Think of it as termux for the VR/AR headset browser, which can be used to e.g. livecode (using terminal auto-completion!) for XR component (registries).
+ scale: 3,
+ events: ['click','keydown'],
+ html: (me) => `
-
-
+
Welcome to XRSHell
+
+
+
+ The
xrsh (xrshell) brings the
FOSS- and
Linux-soul to
WebXR, promoting the use of (interactive text) terminal and user-provided operating systems inside WebXR.
+
Technically,
xrsh is a bundle of freshly created re-usable FOSS WebXR components.
These provide a common filesystem interface for interacting with WebXR, offering the well-known linux/unix toolchain including a commandline to invoke, store, edit and run WebXR utilities - regardless of their implementation.
+
Think of it as termux for the VR/AR headset browser, which can be used to e.g. livecode (using terminal auto-completion!) for XR component (registries).
-
`,
- css: `
- #manual {
- padding:15px;
+
+
+
+
`,
+ css: `.manual {
+ div {
+ padding:11px;
+ }
}
- #manual img{
- width: 100%;
- max-width: 550px;
- border-radius: 7px;
- }
-
- `
+ `,
},
events:{
- DOMready: function(e){},
- click: function(e){}, // click was detected on this.el.dom or AFRAME entity
- },
- init: function () {
- this.require( this.requires )
+ // 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){ }, //
+ keydown: function(e){ }, //
+
+ // 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 )
+
+ },
+
+ DOMready: function( ){
+ console.log("this.el.dom has been added to DOM")
+ this.data.myvalue = 1
+ setInterval( () => this.data.myvalue++, 100 )
+ }
+
},
manifest: { // HTML5 manifest to identify app to xrsh
- "short_name": "XRSH Manual",
- "name": "XRSH Manual",
+ "short_name": "Hello world",
+ "name": "Hello world",
"icons": [
{
"src": "/images/icons-vector.svg",
@@ -67,10 +101,10 @@ AFRAME.registerComponent('manual', {
{
"name": "What is the latest news?",
"cli":{
- "usage": "man xrsh",
- "example": "",
+ "usage": "helloworld [options]",
+ "example": "helloworld news",
"args":{
- "topic": {type:"string"}
+ "--latest": {type:"string"}
}
},
"short_name": "Today",
@@ -79,7 +113,7 @@ AFRAME.registerComponent('manual', {
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
}
],
- "description": "XRSH Manual information",
+ "description": "Hello world information",
"screenshots": [
{
"src": "/images/screenshot1.png",
diff --git a/com/app.js b/com/app.js
index 9e87b60..cfecab0 100644
--- a/com/app.js
+++ b/com/app.js
@@ -53,33 +53,35 @@ AFRAME.registerComponent('app', {
let deps = []
if( !packages.map ) packages = Object.values(packages)
packages.map( (package) => {
- let id = package.split("/").pop()
- // prevent duplicate requests
- if( AFRAME.required[id] ) return
- AFRAME.required[id] = true
+ try{
+ let id = package.split("/").pop()
+ // prevent duplicate requests
+ if( AFRAME.required[id] ) return
+ AFRAME.required[id] = true
- if( !document.head.querySelector(`script#${id}`) ){
- let {id,component,type} = this.parseAppURI(package)
- let p = new Promise( (resolve,reject) => {
- switch(type){
- case "js": let script = document.createElement("script")
- script.id = id
- script.src = package
- script.onload = () => resolve()
- script.onerror = (e) => reject(e)
- document.head.appendChild(script)
- break;
- case "css": let link = document.createElement("link")
- link.id = id
- link.href = package
- link.rel = 'stylesheet'
- document.head.appendChild(link)
- resolve()
- break;
- }
- })
- deps.push(p)
- }
+ if( !document.head.querySelector(`script#${id}`) ){
+ let {component,type} = this.parseAppURI(package)
+ let p = new Promise( (resolve,reject) => {
+ switch(type){
+ case "js": let script = document.createElement("script")
+ script.id = id
+ script.src = package
+ script.onload = () => resolve()
+ script.onerror = (e) => reject(e)
+ document.head.appendChild(script)
+ break;
+ case "css": let link = document.createElement("link")
+ link.id = id
+ link.href = package
+ link.rel = 'stylesheet'
+ document.head.appendChild(link)
+ resolve()
+ break;
+ }
+ })
+ deps.push(p)
+ }
+ }catch(e){ console.error(`package ${package} could not be retrieved..aborting :(`); throw e; }
})
Promise.all(deps).then( () => this.el.emit( readyEvent||'ready', packages) )
}
@@ -106,15 +108,24 @@ AFRAME.AComponent.prototype.initComponent = function(initComponent){
AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
return function(){
updateProperties.apply(this,arguments)
- if( this.dom && this.data && this.data.uri ){
+
+
+ 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})
+ }
+ })
+
+ if( !this.data ) return
+
+ // reactify components with dom-definition
+ if( this.data.uri && this.dom && !this.el.dom ){
tasks = {
- generateUniqueId: () => {
- this.el.uid = String(Math.random()).substr(10)
- return tasks
- },
-
ensureOverlay: () => {
let overlay = document.querySelector('#overlay')
if( !overlay ){
@@ -128,13 +139,6 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
},
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)
@@ -155,35 +159,38 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
return tasks
},
- addModalFunctions: () => {
- this.el.close = () => {
- this.el.dom.remove()
- this.el.removeAttribute("html")
- }
+ requireDependencies: () => {
+ this.require( this.requires )
+ return tasks
+ },
+
+ setupListeners: () => {
+ this.scene.addEventListener('apps:2D', () => this.el.setAttribute('visible', false) )
+ this.scene.addEventListener('apps:XR', () => {
+ this.el.setAttribute('visible', true)
+ this.el.setAttribute("html",`html:#${this.el.uid}; cursor:#cursor`)
+ })
return tasks
}
+
}
tasks
- .generateUniqueId()
.ensureOverlay()
.addCSS()
.createReactiveDOMElement()
.scaleDOMvsXR()
- .addModalFunctions()
+ .requireDependencies()
+ .setupListeners()
tasks.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)
}
}( 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 += `