diff --git a/example/aframe/sandbox/index.html b/example/aframe/sandbox/index.html
index 8d1e280..dffcb21 100644
--- a/example/aframe/sandbox/index.html
+++ b/example/aframe/sandbox/index.html
@@ -86,6 +86,7 @@
+
diff --git a/make b/make
index 970ab19..def37ea 100755
--- a/make
+++ b/make
@@ -123,10 +123,11 @@ build(){
cp src/3rd/js/plugin/frontend/\$editor.js dist/xrfragment.plugin.editor.js
cp src/3rd/js/plugin/frontend/css.js dist/xrfragment.plugin.frontend.css.js
- jscat src/3rd/js/plugin/frontend/{snackbar,accessibility,\$menu,frontend,chatcommand/*,joystick,tab-to-href,remotestorage}.js > dist/xrfragment.plugin.frontend.js
+ jscat src/3rd/js/plugin/frontend/{snackbar,accessibility,\$menu,\$files,frontend,chatcommand/*,joystick,tab-to-href}.js > dist/xrfragment.plugin.frontend.js
jscat src/3rd/js/plugin/matrix/{matrix-crdt,matrix}.js > dist/xrfragment.plugin.matrix.js
jscat src/3rd/js/plugin/p2p/{trystero-torrent.min,trystero}.js > dist/xrfragment.plugin.p2p.js
+ jscat src/3rd/js/plugin/remotestorage/*.js > dist/xrfragment.plugin.remotestorage.js
# all in one
cat dist/aframe.min.js dist/aframe-blink-controls.min.js dist/xrfragment.aframe.js > dist/xrfragment.aframe.all.js
diff --git a/src/3rd/js/plugin/frontend/$files.js b/src/3rd/js/plugin/frontend/$files.js
new file mode 100644
index 0000000..f32fc37
--- /dev/null
+++ b/src/3rd/js/plugin/frontend/$files.js
@@ -0,0 +1,113 @@
+
+// reactive component for displaying the menu
+filesComponent = (el) => new Proxy({
+
+ html: (data) => `
+
+
+
+
+
+
+
+
+
+ ${data.tabs.map( (t) =>
+ `
+
+ `
+ ).join('')
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ tabs: [
+ {name: "local files", id: "localFiles"}
+ ],
+
+ show: false,
+ $localFiles: $localFiles = el.querySelector("#localFiles"),
+
+ toggle(state){
+ this.show = state !== undefined ? state : !this.show
+ },
+
+ init(opts){
+ this.decorateFileButton()
+ // create HTML element
+ el.innerHTML = this.html(this)
+ for( i in this ) el[i] = this[i]
+ this.toggle(this.show) // trigger visibility
+ document.querySelector("#messages").appendChild(el);
+ // setup input listeners
+ (['click']).map( (e) => el.addEventListener(e, (ev) => this[e] && this[e](ev.target.id,ev) ) )
+
+ // signal ready
+ setTimeout( () => {
+ document.dispatchEvent( new CustomEvent("$files:ready", {detail: {$files:this,xrf}}) )
+ },100)
+ return this
+ },
+
+ decorateFileButton: function(){
+ // rename button
+ document.querySelector("#load").setAttribute("value","3D file")
+ // decorate fileLoaders
+ window.frontend.fileLoaders = ( (fileLoaders) => {
+ this.fileLoader = fileLoaders
+ return () => this.toggle()
+ })( window.frontend.fileLoaders )
+ },
+
+ click(id,e){
+ switch(id){
+ case "icon":
+ case "more": return this.toggle(); break;
+ }
+ }
+},
+{
+
+ get(me,k,v){ return me[k] },
+
+ set(me,k,v){
+ me[k] = v
+ switch( k ){
+ case "show":{
+ el.style.display = v ? '' : 'none';
+ if( v ) $chat.visible = true
+ break;
+ }
+ case "tabs":{
+ el.innerHTML = $files.html($files)
+ break;
+ }
+ }
+ },
+
+ renderButtons: (data) => `${data.buttons.join('')}`
+
+})
+
+// reactify component!
+document.addEventListener('$chat:ready', (e) => {
+ window.$files = filesComponent( document.createElement('div') ).init(e.detail)
+})
diff --git a/src/3rd/js/plugin/frontend/css.js b/src/3rd/js/plugin/frontend/css.js
index 6a0f430..ba6ff6a 100644
--- a/src/3rd/js/plugin/frontend/css.js
+++ b/src/3rd/js/plugin/frontend/css.js
@@ -740,6 +740,7 @@ document.head.innerHTML += `
box-sizing: border-box;
position: relative;
display: inline-block;
+ margin-right:5px;
transform: scale(var(--ggs,1)) translate(3px,3px);
width: 16px;
height: 6px;
@@ -847,5 +848,47 @@ document.head.innerHTML += `
left: -11px;
top: -2px
}
+
+ .gg-software-upload {
+ box-sizing: border-box;
+ position: relative;
+ display: inline-block;
+ margin-right:5px;
+ transform: scale(var(--ggs, 1));
+ width: 16px;
+ height: 6px;
+ border: 2px solid;
+ border-top: 0;
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+ margin-top: 8px;
+ }
+ .gg-software-upload::after {
+ content: "";
+ display: block;
+ box-sizing: border-box;
+ position: absolute;
+ width: 8px;
+ height: 8px;
+ border-left: 2px solid;
+ border-top: 2px solid;
+ transform: rotate(45deg);
+ left: 2px;
+ bottom: 4px;
+ }
+ .gg-software-upload::before {
+ content: "";
+ display: block;
+ box-sizing: border-box;
+ position: absolute;
+ border-radius: 3px;
+ width: 2px;
+ height: 10px;
+ background: currentColor;
+ left: 5px;
+ bottom: 3px;
+ }
+
+
`
diff --git a/src/3rd/js/plugin/frontend/filedialog.js b/src/3rd/js/plugin/frontend/filedialog.js
new file mode 100644
index 0000000..fd85c00
--- /dev/null
+++ b/src/3rd/js/plugin/frontend/filedialog.js
@@ -0,0 +1,94 @@
+
+// reactive component for displaying the menu
+filesComponent = (el) => new Proxy({
+
+ html: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+ `,
+
+ collapsed: false,
+ $localFiles: $localFiles = el.querySelector("#localFiles"),
+
+ toggle(state){
+ this.collapsed = state !== undefined ? state : !this.collapsed
+ },
+
+ init(opts){
+ el.innerHTML = this.html
+ document.querySelector("#messages").appendChild(el);
+ (['click']).map( (e) => el.addEventListener(e, (ev) => this[e] && this[e](ev.target.id,ev) ) )
+ setTimeout( () => {
+ document.dispatchEvent( new CustomEvent("$files:ready", {detail: {$files:this,xrf}}) )
+ },100)
+ return this
+ },
+
+ click(id,e){
+ switch(id){
+ case "icon":
+ case "more": return this.toggle(); break;
+ }
+ this.toggle(false)
+ }
+},
+{
+
+ get(me,k,v){ return me[k] },
+
+ set(me,k,v){
+ me[k] = v
+ switch( k ){
+ case "buttons": el.querySelector("#buttons").innerHTML = this.renderButtons(me);
+ document.dispatchEvent( new CustomEvent("$menu:buttons:render", {detail: el.querySelector('.menu') }) )
+ break;
+ case "collapsed":
+ el.querySelector("#buttons").style.display = me.collapsed ? 'block' : 'none'
+ frontend.emit('$menu:collapse', v)
+ break;
+ }
+ },
+
+ renderButtons: (data) => `${data.buttons.join('')}`
+
+})
+
+// reactify component!
+document.addEventListener('frontend:ready', (e) => {
+ window.$files = filesComponent( document.createElement('div') ).init(e.detail)
+})
diff --git a/src/3rd/js/plugin/frontend/remotestorage.js b/src/3rd/js/plugin/frontend/remotestorage.js
deleted file mode 100644
index 5176db6..0000000
--- a/src/3rd/js/plugin/frontend/remotestorage.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// this demonstrates the remotestorage aframe component
-
-
-document.addEventListener('frontend:ready', (e) => {
-
- function loadScript(cb){
- let el = document.createElement("script")
- el.setAttribute("defer","")
- el.src = "https://unpkg.com/remotestoragejs@2.0.0-beta.7/release/remotestorage.js"
- document.head.appendChild(el)
-
- el = document.createElement("script")
- el.src = "https://unpkg.com/remotestorage-widget@1.6.0/build/widget.js"
- el.addEventListener('load', () => setTimeout(cb,2000) )
- document.head.appendChild(el)
- }
-
- function addStyles(){
- let el = document.createElement('style')
- el.type = 'text/css'
- el.innerHTML = `
-
- #remotestorage-widget {
- left: 0;
- position: fixed;
- display:block;
- }
- body.menu #remotestorage-widget{
- top: 50px;
- }
- body #remotestorage-widget {
- top: 0px;
- }
- `
- document.head.appendChild(el)
- }
-
- function addButtons(){
- const $widget = document.querySelector(".rs-widget")
- debugger
-
- const $btnLoad = document.createElement('button')
- const $btnSave = document.createElement('button')
- $btnLoad.innerText = 'load'
- $btnSave.innerText = 'save'
- $widget.appendChild($btnLoad)
- $widget.appendChild($btnSave)
- }
-
- function init(){
- let apis = {}
- window.remoteStorage = new RemoteStorage({logging: true })
- if( Object.keys(apis).length ) remoteStorage.setApiKeys(apis)
-
- remoteStorage.on('connected', (e) => { console.log("connected") } )
- //remoteStorage.on('network-offline', (e) => this.el.sceneEl.emit('remoteStorage.network-offline',e) )
- //remoteStorage.on('network-online', (e) => this.el.sceneEl.emit('remoteStorage.network-online',e) )
- //remoteStorage.on('error', (e) => this.el.sceneEl.emit('remoteStorage.error',e) )
- //remoteStorage.on('ready', (e) => { } )
-
- remoteStorage.access.claim( `webxr`, 'rw'); // our data dir
- remoteStorage.caching.enable( `/webxr/` ) // local-first, remotestorage-second
-
- }
-
- addStyles()
- loadScript(init)
-
- // decorate fileLoaders
- window.frontend.fileLoaders = (function(fileLoaders){
- return function(){
- // show rs widgets
- if( !document.querySelector("#remotestorage-widget") ){
- let opts = {}
- opts.modalBackdrop = false
- widget = new window.Widget(window.remoteStorage, opts)
- widget.attach();
- addButtons()
- }
-
- //fileLoaders()
- }
- })( window.frontend.fileLoaders )
-})
diff --git a/src/3rd/js/plugin/remotestorage/remotestorage.js b/src/3rd/js/plugin/remotestorage/remotestorage.js
new file mode 100644
index 0000000..53287ca
--- /dev/null
+++ b/src/3rd/js/plugin/remotestorage/remotestorage.js
@@ -0,0 +1,105 @@
+// this demonstrates the remotestorage aframe component
+
+// reactive component for displaying the menu
+remoteStorageComponent = (el) => new Proxy({
+
+ html: `
+
+
+
+
+
+
+
+
+ `,
+
+ show: false,
+
+ toggle(state){
+ this.show = state !== undefined ? state : !this.show
+ },
+
+ init(opts){
+ // create HTML element
+ $files.tabs = $files.tabs.concat({id:"remoteFiles", name: "remote files"})
+ el.innerHTML = this.html
+ el.className = "tab"
+ this.toggle(this.show) // trigger visibility
+ document.querySelector("#files .tab-frame").appendChild(el);
+
+ // setup input listeners
+ (['click']).map( (e) => el.addEventListener(e, (ev) => typeof this[e] == 'function' && this[e](ev.target.id,ev) ) )
+
+ // signal ready
+ setTimeout( () => {
+ document.dispatchEvent( new CustomEvent("remotestorage:ready", {detail: {$files:this,xrf}}) )
+ },100)
+
+ this.loadScript( () => this.initRemoteStorage() )
+
+ return this
+ },
+
+ loadScript(cb){
+ let el = document.createElement("script")
+ el.setAttribute("defer","")
+ el.src = "https://unpkg.com/remotestoragejs@2.0.0-beta.7/release/remotestorage.js"
+ document.head.appendChild(el)
+
+ el = document.createElement("script")
+ el.src = "https://unpkg.com/remotestorage-widget@1.6.0/build/widget.js"
+ el.addEventListener('load', () => setTimeout(cb,2000) )
+ document.head.appendChild(el)
+ },
+
+ initRemoteStorage(){
+ let apis = {
+ dropbox: "4jc8nx1lbarp472"
+ }
+ window.remoteStorage = new RemoteStorage({logging: true })
+ if( Object.keys(apis).length ) remoteStorage.setApiKeys(apis)
+
+ remoteStorage.on('connected', (e) => { console.log("connected") } )
+ //remoteStorage.on('network-offline', (e) => this.el.sceneEl.emit('remoteStorage.network-offline',e) )
+ //remoteStorage.on('network-online', (e) => this.el.sceneEl.emit('remoteStorage.network-online',e) )
+ //remoteStorage.on('error', (e) => this.el.sceneEl.emit('remoteStorage.error',e) )
+ //remoteStorage.on('ready', (e) => { } )
+
+ remoteStorage.access.claim( `webxr`, 'rw'); // our data dir
+ remoteStorage.caching.enable( `/webxr/` ) // local-first, remotestorage-second
+
+ // create widget
+ let opts = {}
+ opts.modalBackdrop = false
+ widget = new window.Widget(window.remoteStorage, opts)
+ widget.attach( "rswidget" );
+
+ },
+
+ click(id,e){
+ switch(id){
+ case "icon":
+ case "more": return this.toggle(); break;
+ }
+ this.toggle(false)
+ }
+},
+{
+
+ get(me,k,v){ return me[k] },
+
+ set(me,k,v){
+ me[k] = v
+ },
+
+})
+
+// reactify component!
+document.addEventListener('$files:ready', (e) => {
+ window.$remotestorage = remoteStorageComponent( document.createElement('div') ).init(e.detail)
+})