Browse Source

Merge remote-tracking branch 'upstream/lcs2New'

Nikolay Suslov 5 years ago
parent
commit
f6e68a26da

+ 532 - 111
public/app.js

@@ -2,6 +2,7 @@ import page from '/lib/page.mjs';
 import { Lang } from '/lib/polyglot/language.js';
 import { Helpers } from '/helpers.js';
 import { IndexApp } from '/web/index-app.js';
+import { WorldApp } from '/web/world-app.js';
 import { Widgets } from '/lib/widgets.js';
 
 
@@ -22,7 +23,7 @@ class App {
 
       this.helpers = new Helpers;
       this.initUser();
-      
+
       //client routes
       page('/', this.HandleIndex);
       page('/setup', this.HandleSetupIndex);
@@ -33,9 +34,11 @@ class App {
       page('/:user/worlds/:type', this.HandleUserWorldsWithType);
       page('/:user/:type/:name/edit/:file', this.HandleFileEdit);
       page('/:user/:space', this.HandleParsableRequestGenID);
+      page('/:user/:space/about', this.HandleWorldAbout);
       page('/:user/:space/:id', this.HandleParsableRequestWithID);
       page('/:user/:space/index.vwf/:id', this.HandleParsableRequestWithID);
       page('/:user/:space/load/:savename', this.HandleParsableLoadRequest);
+      page('/:user/:space/load/:savename/about', this.HandleWorldAbout);
       page('/:user/:space/:id/load/:savename', this.HandleParsableRequestWithID);
 
       page('/:user/:space/load/:savename/:rev', this.HandleParsableLoadRequestWithRev);
@@ -185,7 +188,7 @@ class App {
 
   async loadWorldsDefaults(replace) {
 
-   //load to DB default worlds
+    //load to DB default worlds
 
     let worldsResponse = await fetch('/world-files', { method: 'get' });
     let worldFiles = await worldsResponse.json();
@@ -236,31 +239,31 @@ class App {
 
     console.log(worldsObj);
 
-    if(replace){
+    if (replace) {
 
-    Object.entries(worldsObj).forEach(el => {
+      Object.entries(worldsObj).forEach(el => {
 
-      let worldName = el[0];
-      let files = el[1];
-      Object.entries(files).forEach(file => {
+        let worldName = el[0];
+        let files = el[1];
+        Object.entries(files).forEach(file => {
 
-        _LCSDB.user().get('worlds').get(worldName).get(file[0]).put(file[1]);
+          _LCSDB.user().get('worlds').get(worldName).get(file[0]).put(file[1]);
 
+        })
       })
-    })
-  } else {
-    //force replace all default worlds
+    } else {
+      //force replace all default worlds
 
       Object.entries(worldsObj).forEach(el => {
 
         let worldName = el[0];
         let files = el[1];
         Object.entries(files).forEach(file => {
-  
-          _LCSDB.user().get('worlds').get(worldName).get(file[0]).not(res=>{
+
+          _LCSDB.user().get('worlds').get(worldName).get(file[0]).not(res => {
             _LCSDB.user().get('worlds').get(worldName).get(file[0]).put(file[1]);
           })
-  
+
         })
       })
 
@@ -270,10 +273,10 @@ class App {
 
   async loadEmptyDefaultProto() {
 
-     //empty proto world
-     let userPub = _LCSUSER.is.pub;
-     let worldsObj = {};
-     let emptyWorld = {
+    //empty proto world
+    let userPub = _LCSUSER.is.pub;
+    let worldsObj = {};
+    let emptyWorld = {
 
       "index_vwf_yaml": YAML.stringify(
         {
@@ -282,33 +285,33 @@ class App {
 
       "index_vwf_config_yaml": YAML.stringify(
         {
+          "info": {
+            "title": "Empty World"
+          },
+          "model": {
+            "vwf/model/aframe": null
+          },
+          "view": {
+            "vwf/view/aframe": null,
+            "vwf/view/editor-new": null
+          }
+        }, 4),
+      "assets_json": JSON.stringify({}),
+      "index_vwf_html": JSON.stringify("<!-- DEFAULT HTML -->"),
+      "appui_js": JSON.stringify("//appui in JS"),
+      "info_json": JSON.stringify({
         "info": {
-          "title": "Empty World"
-        },
-        "model": {
-          "vwf/model/aframe": null
-        },
-        "view": {
-          "vwf/view/aframe": null,
-          "vwf/view/editor-new": null
-        } 
-      }, 4),
-      "assets_json": JSON.stringify ({}),
-      "index_vwf_html": JSON.stringify ("<!-- DEFAULT HTML -->"),
-      "appui_js": JSON.stringify ("//appui in JS"),
-      "info_json": JSON.stringify ({ 
-        "info": { 
           "en": {
-              "title": "Empty World",
-              "imgUrl": "",
-              "text": "Empty World"
+            "title": "Empty World",
+            "imgUrl": "",
+            "text": "Empty World"
           },
           "ru": {
-              "title": "Новый Мир",
-              "imgUrl": "",
-              "text": "Новый Мир"
+            "title": "Новый Мир",
+            "imgUrl": "",
+            "text": "Новый Мир"
           }
-      }
+        }
       }, null, 4)
     }
 
@@ -319,7 +322,7 @@ class App {
       'published': true
     }
 
-    Object.keys(emptyWorld).forEach(el=>{
+    Object.keys(emptyWorld).forEach(el => {
       //let modified = new Date().valueOf();
       let created = new Date().valueOf();
       let obj = {
@@ -328,7 +331,7 @@ class App {
         'created': created
       }
       worldsObj['empty'][el] = obj;
-    }) 
+    })
 
     console.log(worldsObj);
 
@@ -360,6 +363,28 @@ class App {
 
   }
 
+  async HandleWorldAbout(ctx) {
+
+    console.log("about world");
+
+    let userAlias = ctx.params.user;
+    let worldName = ctx.params.space;
+    let saveName = ctx.params.savename;
+
+    window._app.hideProgressBar();
+    window._app.hideUIControl();
+
+    if (!_app.indexApp) {
+      _app.indexApp = new IndexApp;
+      _app.indexApp.initHTML();
+      _app.indexApp.initApp();
+    }
+
+    let worldApp = new WorldApp(userAlias, worldName, saveName);
+    await worldApp.initWorldGUI();
+
+  }
+
   HandleSetupIndex() {
 
     window._app.hideProgressBar();
@@ -369,7 +394,7 @@ class App {
     el.setAttribute("id", "admin");
     document.body.appendChild(el);
 
-   
+
     _LCSDB.on('auth',
       async function (ack) {
 
@@ -383,7 +408,7 @@ class App {
                 $type: "p",
                 class: "mdc-typography--headline5",
                 $text: "1. Set app system user PUB key"
-            },
+              },
               {
                 $type: "button",
                 class: "mdc-button mdc-button--raised",
@@ -394,11 +419,11 @@ class App {
                 }
               }
             ]
-      
+
           }
-      
+
           let adminComponents = [];
-      
+
 
           let defaultPub = await _LCSDB.get('lcs/app').get('pub').once().then();
           if (!defaultPub) {
@@ -414,7 +439,7 @@ class App {
                   $type: "p",
                   class: "mdc-typography--headline5",
                   $text: "3. Initialize empty World proto"
-              },
+                },
                 {
                   $type: "button",
                   id: "loadDefaults",
@@ -427,7 +452,7 @@ class App {
                 }
               ]
             }
-            
+
 
             let loadDefaults = {
               $cell: true,
@@ -437,7 +462,7 @@ class App {
                   $type: "p",
                   class: "mdc-typography--headline5",
                   $text: "4. Load Sample Worlds protos from server (optional)"
-              },
+                },
                 {
                   $type: "button",
                   id: "loadDefaults",
@@ -456,17 +481,17 @@ class App {
                 _cellWidgets.switch({
                   'id': 'forceReplace',
                   'init': function () {
-                      this._switch = new mdc.switchControl.MDCSwitch(this);
-                      this._replaceSwitch = this._switch;
-                      this._switch.checked = false;
+                    this._switch = new mdc.switchControl.MDCSwitch(this);
+                    this._replaceSwitch = this._switch;
+                    this._switch.checked = false;
                   }
-              }
-              ),
-              {
-                $type: 'label',
-                for: 'input-forceReplace',
-                $text: 'Force replace'
-              }
+                }
+                ),
+                {
+                  $type: 'label',
+                  for: 'input-forceReplace',
+                  $text: 'Force replace'
+                }
 
               ]
             }
@@ -478,7 +503,7 @@ class App {
                   $type: "p",
                   class: "mdc-typography--headline5",
                   $text: "3. Load VWF & A-Frame default components"
-              },
+                },
                 {
                   $type: "button",
                   class: "mdc-button mdc-button--raised",
@@ -514,39 +539,39 @@ class App {
 
     _LCSDB.on('auth',
       async function (ack) {
-        if(ack.pub){
+        if (ack.pub) {
           document.querySelector("#profile")._status = "User: " + _LCSUSER.is.alias //+' pub: ' + _LCSUSER.is.pub;
           document.querySelector("#profile").$update();
         }
       })
 
-      let el = document.createElement("div");
-      el.setAttribute("id", "userProfile");
-      document.body.appendChild(el);
-
-      let userProfile = {
-        $type: 'div',
-        id: "profile",
-        _status: "",
-        $init: function(){
-          this._status = "user is not signed in..."
-        },
-        $update: function(){
-          this.$components = [
-            {
-                $type: "h1",
-                class: "mdc-typography--headline4",
-                $text: this._status //"Profile for: " + _LCSUSER.is.alias
-            }
-          ]
-        }
+    let el = document.createElement("div");
+    el.setAttribute("id", "userProfile");
+    document.body.appendChild(el);
+
+    let userProfile = {
+      $type: 'div',
+      id: "profile",
+      _status: "",
+      $init: function () {
+        this._status = "user is not signed in..."
+      },
+      $update: function () {
+        this.$components = [
+          {
+            $type: "h1",
+            class: "mdc-typography--headline4",
+            $text: this._status //"Profile for: " + _LCSUSER.is.alias
+          }
+        ]
       }
+    }
 
-      document.querySelector("#userProfile").$cell({
-        $cell: true,
-        $type: "div",
-        $components: [userProfile]
-      })
+    document.querySelector("#userProfile").$cell({
+      $cell: true,
+      $type: "div",
+      $components: [userProfile]
+    })
   }
 
 
@@ -619,7 +644,7 @@ class App {
                       editor.getSession().setMode(mode);
                       editor.setOptions({
                         maxLines: Infinity
-                    });
+                      });
                     }
                   },
                   {
@@ -644,10 +669,11 @@ class App {
                     $text: "Close",
                     onclick: function (e) {
                       console.log("close");
-                      if (type == "proto")
-                        window.location.pathname = "/" + user + '/worlds/protos'
-                      if (type == "state")
-                        window.location.pathname = "/" + user + '/worlds/states'
+                      window.history.back();
+                      // if (type == "proto")
+                      //   window.location.pathname = "/" + user + '/' + worldName + '/about'
+                      // if (type == "state")
+                      //   window.location.pathname = "/" + user + '/' + worldName + '/about'
                     }
                   }
                 ]
@@ -676,17 +702,16 @@ class App {
 
     if (!_app.indexApp) {
       _app.indexApp = new IndexApp;
-      document.querySelector('head').innerHTML += '<link rel="stylesheet" href="/web/index-app.css">';
-    }
-
-    if (!document.querySelector('#app')) {
-      await _app.indexApp.initApp();
+      _app.indexApp.initHTML();
+      _app.indexApp.initApp();
     }
 
     if (type == 'protos') {
-      await _app.indexApp.getWorldsProtosFromUserDB(user);
+      await _app.indexApp.initWorldsProtosListForUser(user)//.getWorldsProtosListForUser(user); 
     } else if (type == 'states') {
-      await _app.indexApp.getWorldsFromUserDB(user);
+      await _app.indexApp.initWorldsStatesListForUser(user);
+
+      //await _app.indexApp.getWorldsFromUserDB(user);
     }
 
   }
@@ -698,12 +723,15 @@ class App {
     window._app.hideProgressBar();
     window._app.hideUIControl();
 
-    _app.indexApp = new IndexApp;
-    document.querySelector('head').innerHTML += '<link rel="stylesheet" href="/web/index-app.css">';
+    if (!_app.indexApp) {
+      _app.indexApp = new IndexApp;
+      await _app.indexApp.generateFrontPage();
+      _app.indexApp.initHTML();
+    }
 
-    await _app.indexApp.generateFrontPage();
-    await _app.indexApp.initApp();
-    await _app.indexApp.getAppDetailsFromDB();
+    _app.indexApp.initApp();
+    await _app.indexApp.initWorldsProtosListForUser('app');
+    //await _app.indexApp.getAppDetailsFromDB();
 
   }
 
@@ -781,7 +809,7 @@ class App {
 
   }
 
-  
+
   async HandleParsableRequestWithID(ctx) {
 
     let app = window._app;
@@ -797,7 +825,7 @@ class App {
 
     let pathToParse = pathname.replace('/' + user, "");
 
-    app.helpers.Process(pathToParse).then(async function(parsedRequest) {
+    app.helpers.Process(pathToParse).then(async function (parsedRequest) {
 
       localStorage.setItem('lcs_app', JSON.stringify({ path: parsedRequest }));
       console.log(parsedRequest);
@@ -827,7 +855,7 @@ class App {
 
       // Redirect if the url request does not include an application/file && a default 'index.vwf.yaml' exists
       // page.redirect(pathname + '/' + app.helpers.GenerateInstanceID());
-      window.location.pathname = pathname + '/' + app.helpers.GenerateInstanceID() 
+      window.location.pathname = pathname + '/' + app.helpers.GenerateInstanceID()
 
       //return true;
     } else {
@@ -952,6 +980,10 @@ class App {
 
     //let objName = loadInfo[ 'save_name' ] +'/'+ "savestate_" + loadInfo[ 'save_revision' ];
 
+    if (!loadInfo.save_name) {
+      return undefined
+    }
+
     let objName = "savestate_" + loadInfo['application_path'] + '/' + loadInfo['save_name'] + '_vwf_json';
     let objNameRev = "savestate_" + loadInfo['save_revision'] + loadInfo['application_path'] + '/' + loadInfo['save_name'] + '_vwf_json';
 
@@ -1035,6 +1067,8 @@ class App {
 
   async cloneWorldPrototype(worldName, userName, newWorldName) {
 
+    _app.showProgressBar();
+
     let userPub = await _LCSDB.get('users').get(userName).get('pub').once().then();
     //let worldProto = await _LCSDB.user(userPub).get('worlds').get(worldName).once().then();
 
@@ -1047,6 +1081,7 @@ class App {
     console.log('clone: ' + worldName + 'to: ' + worldID);
 
     let newOwner = _LCSUSER.is.pub;
+    let created = new Date().valueOf();
 
     let worldObj = {
       'owner': newOwner,
@@ -1064,15 +1099,50 @@ class App {
       let res = await _LCSDB.user(userPub).get('worlds').get(worldName).get(fn).once().then();
       let data = {
         'file': res.file,
-        'modified': res.modified
+        'modified': created
       }
       worldObj[fn] = data;
     }
     console.log(worldObj);
 
-    Object.keys(worldObj).forEach(el => {
-      _LCSUSER.get('worlds').get(worldID).get(el).put(worldObj[el]);
-    })
+    for (const el of Object.keys(worldObj)) {
+      await _LCSUSER.get('worlds').get(worldID).get(el).put(worldObj[el]).then();
+    }
+
+    _app.hideProgressBar();
+    console.log('CLONED!!!');
+
+    let appEl = document.createElement("div");
+    appEl.setAttribute("id", 'cloneLink');
+    let entry = document.querySelector('#worldActionsGUI');
+    if (entry) {
+      entry.appendChild(appEl);
+
+      document.querySelector("#cloneLink").$cell({
+        id: 'cloneLink',
+        $cell: true,
+        $type: "div",
+        $components: [
+          {
+            $type: "a",
+            class: "mdc-button mdc-button--raised mdc-card__action",
+            $text: "Go to new cloned World!",
+            onclick: function (e) {
+              window.location.pathname = '/' + userName + '/' + worldID + '/about'
+            }
+          }
+        ]
+      })
+    }
+
+    //window.location.pathname = '/' + userName + '/' + worldID + '/about'
+    //page()
+
+    // Object.keys(worldObj).forEach(el => {
+    //   _LCSUSER.get('worlds').get(worldID).get(el).put(worldObj[el]);
+    // })
+
+
   }
 
   async cloneWorldState(filename) {
@@ -1357,6 +1427,8 @@ class App {
     var progressbar = document.getElementById("load-progressbar");
     if (progressbar) {
       progressbar.classList.remove("visible");
+      progressbar.classList.remove("mdc-linear-progress--indeterminate");
+
       progressbar.classList.add("not-visible");
       progressbar.classList.add("mdc-linear-progress--closed");
 
@@ -1368,12 +1440,361 @@ class App {
 
     let progressbar = document.getElementById("load-progressbar");
     if (progressbar) {
+      progressbar.classList.remove("not-visible");
+      progressbar.classList.remove("mdc-linear-progress--closed");
+
       progressbar.classList.add("visible");
       progressbar.classList.add("mdc-linear-progress--indeterminate");
     }
   }
 
+  // SUPPORT of DELETE USER WORLDS & SAVE STATES (experimental)
+  // TODO: manual garbage collection
+
+  async deleteWorldState(worldName, indexState) {
+
+    let revs = await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').once().then();
+    if (revs) {
+      for (const el of Object.keys(revs)) {
+        if (el !== '_') {
+          let doc = await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').get(el).once().then();
+          for (const rev of Object.keys(doc)) {
+            if (rev !== '_') {
+              await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').get(el).get(rev).put(null).then();
+            }
+          }
+          await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').get(el).put(null).then();
+        }
+      }
+    }
+
+    // clear all state params
+    let stateDoc = await _LCSUSER.get('documents').get(worldName).get(indexState).once().then();
+    for (const state of Object.keys(stateDoc)) {
+      if (state !== '_' && state !== 'revs') {
+        await _LCSUSER.get('documents').get(worldName).get(indexState).get(state).put(null).then();
+      }
+    }
+
+    await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').put(null).then();
+    await _LCSUSER.get('documents').get(worldName).get(indexState).put(null).then();
+
+  }
+
+  async deleteWorld(name, type) {
+
+    if (type == 'proto') {
+
+      let worldName = name;
+      //TODO check for states (ask for deleting all states first...)
+      //delete states
+
+      let documents = await _LCSUSER.get('documents').once().then();
+      if (documents) {
+        let states = await _LCSUSER.get('documents').get(worldName).once().then();
+        if (states) {
+          for (const el of Object.keys(states)) {
+            if (el !== '_') {
+              if (states[el]) {
+                await this.deleteWorldState(worldName, el);
+              }
+
+            }
+          }
+        }
+      }
+
+      let worldFiles = await _LCSUSER.get('worlds').get(worldName).once().then();
+      if (worldFiles) {
+        for (const el of Object.keys(worldFiles)) {
+          if (el !== '_') {
+            let doc = await _LCSUSER.get('worlds').get(worldName).get(el).once().then();
+            if (doc) {
+              if (doc.file) {
+                for (const fEl of Object.keys(doc)) {
+                  if (fEl !== '_') {
+                    await _LCSUSER.get('worlds').get(worldName).get(el).get(fEl).put(null).then();
+                  }
+                }
+                await _LCSUSER.get('worlds').get(worldName).get(el).put(null).then();
+              } else {
+                await _LCSUSER.get('worlds').get(worldName).get(el).put(null).then()
+              }
+            }
+          }
+        }
+      }
+
+      //  _LCSUSER.get('worlds').get(worldName).map((res, index) => {
+
+      //       if(typeof res == 'object'){
+      //         _LCSUSER.get('worlds').get(worldName).get(index)
+      //         .get('file').put("null")
+      //         .back(1)
+      //         .get('modified').put("null")
+      //         .back(1)
+      //         .get('created').put("null")
+      //         .back(1).put("null")
+      //       } else {
+      //         _LCSUSER.get('worlds').get(worldName).get(index).put("null")
+      //       }
+      //  })
+
+      await _LCSUSER.get('worlds').get(worldName).put(null).then();
+
+    } else if (type == 'state') {
+
+      let worldName = name.split('/')[0];
+      let stateName = name.split('/')[2];
+
+      let stateEntryInfo = 'savestate_/' + worldName + '/' + stateName + '_info_vwf_json';
+      let stateEntry = 'savestate_/' + worldName + '/' + stateName + '_vwf_json';
+      await this.deleteWorldState(worldName, stateEntryInfo);
+      await this.deleteWorldState(worldName, stateEntry);
+    }
+
+    let noty = new Noty({
+      text: "World Deleted!",
+      timeout: 2000,
+      theme: 'mint',
+      layout: 'bottomRight',
+      type: 'success'
+    });
+    noty.show();
+
+  }
+
+
+  parseAppInstancesData(data) {
+
+    let jsonObj = JSON.parse(data);
+    var parsed = {};
+    let listData = {};
+
+    for (var prop in jsonObj) {
+      var name = prop.split('/')[1];
+      if (parsed[name]) {
+        parsed[name][prop] = jsonObj[prop];
+      } else {
+        parsed[name] = {};
+        parsed[name][prop] = jsonObj[prop];
+      }
+
+    }
+    //console.log(parsed);
+    for (var prop in parsed) {
+      var name = prop;
+      let obj = Object.entries(parsed[prop]);
+      var lists = {};
+      obj.forEach(el => {
+        if (el[1].loadInfo['save_name']) {
+          let saveName = prop + '/load/' + el[1].loadInfo.save_name;
+          if (!lists[saveName])
+            lists[saveName] = {};
+          lists[saveName][el[0]] = el[1]
+        } else {
+          if (!lists[name])
+            lists[name] = {};
+          lists[name][el[0]] = el[1]
+        }
+      });
+
+      // console.log(lists);
+
+      Object.entries(lists).forEach(list => {
+        listData[list[0]] = list[1];
+      })
+    }
+
+    return listData
+    // console.log(data)
+  }
+
+  async getAllStateWorldsInfoForUser(userAlias) {
+
+    let userPub = await _LCSDB.get('users').get(userAlias).get('pub').once().then();
+
+    var db = _LCSDB.user(userPub);
+
+    if (_LCSUSER.is) {
+      if (_LCSUSER.is.alias == userAlias)
+        db = _LCSUSER;
+    }
+
+    var states = {};
+
+    let worldDocs = await db.get('worlds').once().then();
+    if (worldDocs) {
+      let protos = Object.keys(worldDocs).filter(el => el !== '_');
+
+      if (protos) {
+        for (const el of protos) {
+          let info = await this.getSaveStates(userAlias, el);
+          if (Object.keys(info).length !== 0)
+            states[el] = info;
+        }
+      }
+    }
+
+    return states
+
+  }
+
+
+  async getAllProtoWorldsInfoForUser(userAlias) {
+
+    let userPub = await _LCSDB.get('users').get(userAlias).get('pub').once().then();
+
+    var db = _LCSDB.user(userPub);
+
+    if (_LCSUSER.is) {
+      if (_LCSUSER.is.alias == userAlias)
+        db = _LCSUSER;
+    }
+
+    var worlds = {};
+
+    let worldDocs = await db.get('worlds').once().then();
+    if (worldDocs) {
+      let protos = Object.keys(worldDocs).filter(el => el !== '_');
+
+      if (protos) {
+        for (const el of protos) {
+          let info = await this.getWorldInfo(userAlias, el);
+          if (Object.keys(info).length !== 0)
+            worlds[el] = info;
+        }
+      }
+    }
+
+    return worlds
+  }
+
+  async getSaveStates(userAlias, worldName) {
+
+    let userPub = await _LCSDB.get('users').get(userAlias).get('pub').once().then();
+
+    var db = _LCSDB.user(userPub);
+
+    if (_LCSUSER.is) {
+      if (_LCSUSER.is.alias == userAlias)
+        db = _LCSUSER;
+    }
+
+    var states = {};
+
+    let documents = await db.get('documents').once().then();
+    if(documents) {
+    let docs = await db.get('documents').get(worldName).once().then();
+    if (docs) {
+      let saves = Object.keys(docs).filter(el => el.includes('_info_vwf_json'));
+      if (saves) {
+        for (const el of saves) {
+          let stateName = el.split('/')[2].replace('_info_vwf_json', "");
+          let info = await this.getStateInfo(userAlias, worldName, stateName);
+          if (Object.keys(info).length !== 0)
+            states[stateName] = info;
+        }
+      }
+    }
+  }
+    return states
+  }
+
+  async getStateInfo(user, space, saveName) {
 
+    let userPub = await _LCSDB.get('users').get(user).get('pub').once().then();
+    var db = _LCSDB.user(userPub);
+
+    if (_LCSUSER.is) {
+      if (_LCSUSER.is.alias == user)
+        db = _LCSUSER;
+    }
+
+    var info = {};
+
+    let docName = 'savestate_/' + space + '/' + saveName + '_info_vwf_json';
+    let world = await db.get('documents').get(space).get(docName).once().then();
+    if (world) {
+      let res = await db.get('documents').get(space).get(docName).once().then();
+
+        if (res && res !== 'null') {
+
+          if (res.file && res.file !== 'null') {
+
+            let worldDesc = JSON.parse(res.file);
+            let root = Object.keys(worldDesc)[0];
+            var appInfo = worldDesc[root]['en'];
+
+            let langID = localStorage.getItem('krestianstvo_locale');
+            if (langID) {
+              appInfo = worldDesc[root][langID]
+            }
+
+            info = {
+              'worldName': space + '/load/' + saveName,
+              'created': res.created ? res.created : res.modified,
+              'modified': res.modified,
+              'type': 'saveState',
+              'userAlias': user,
+              'info': appInfo
+            }
+          }
+        }
+    
+    }
+    return info
+  }
+
+
+  async getWorldInfo(user, space) {
+    //get space for user
+
+    let userPub = await _LCSDB.get('users').get(user).get('pub').once().then();
+    var db = _LCSDB.user(userPub);
+
+    if (_LCSUSER.is) {
+      if (_LCSUSER.is.alias == user)
+        db = _LCSUSER;
+    }
+
+    var info = {};
+
+
+    let world = await db.get('worlds').get(space).once().then();
+    if (world) {
+      let res = await db.get('worlds').get(space).get('info_json').once().then();
+
+        if (res && res !== 'null') {
+
+          if (res.file && res.file !== 'null') {
+
+            let worldDesc = JSON.parse(res.file);
+            let root = Object.keys(worldDesc)[0];
+            var appInfo = worldDesc[root]['en'];
+
+            let langID = localStorage.getItem('krestianstvo_locale');
+            if (langID) {
+              appInfo = worldDesc[root][langID]
+            }
+
+            info = {
+              'worldName': space,
+              'created': res.created ? res.created : res.modified,
+              'modified': res.modified,
+              'type': 'proto',
+              'userAlias': user,
+              'info': appInfo
+            }
+
+          }
+        }
+      
+    }
+
+    return info
+
+  }
 
 
 }

+ 45 - 34
public/lib/gundb/gun.js

@@ -1,22 +1,22 @@
 ;(function(){
 
-	/* UNBUILD */
-	var root;
-	if(typeof window !== "undefined"){ root = window }
-	if(typeof global !== "undefined"){ root = global }
-	root = root || {};
-	var console = root.console || {log: function(){}};
-	function USE(arg){
-		return arg.slice? USE[R(arg)] : function(mod, path){
-			arg(mod = {exports: {}});
-			USE[R(path)] = mod.exports;
-		}
-		function R(p){
-			return p.split('/').slice(-1).toString().replace('.js','');
-		}
-	}
-	if(typeof module !== "undefined"){ var common = module }
-	/* UNBUILD */
+  /* UNBUILD */
+  var root;
+  if(typeof window !== "undefined"){ root = window }
+  if(typeof global !== "undefined"){ root = global }
+  root = root || {};
+  var console = root.console || {log: function(){}};
+  function USE(arg, req){
+    return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
+      arg(mod = {exports: {}});
+      USE[R(path)] = mod.exports;
+    }
+    function R(p){
+      return p.split('/').slice(-1).toString().replace('.js','');
+    }
+  }
+  if(typeof module !== "undefined"){ var common = module }
+  /* UNBUILD */
 
 	;USE(function(module){
 		// Generic javascript utilities.
@@ -638,7 +638,7 @@
 					dup.to = setTimeout(function(){
 						var now = time_is();
 						Type.obj.map(dup.s, function(it, id){
-							if(opt.age > (now - it.was)){ return }
+							if(it && opt.age > (now - it.was)){ return }
 							Type.obj.del(dup.s, id);
 						});
 						dup.to = null;
@@ -1661,7 +1661,8 @@
 				data = tmp.put;
 			}
 			if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) }
-			if(!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))){
+			if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack))))
+			|| (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (link||at).ack <= tmp)){
 				tmp = (eve.wait = {})[at.id] = setTimeout(function(){
 					val.call({as:opt}, msg, eve, tmp || 1);
 				}, opt.wait || 99);
@@ -1901,7 +1902,7 @@
 				if(data){ disk = data }
 				try{store.setItem(opt.prefix, JSON.stringify(disk));
 				}catch(e){ 
-					Gun.log(err = e || "localStorage failure");
+					Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html .");
 					root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush});
 				}
 				if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers.
@@ -1921,7 +1922,10 @@
 
 		function Mesh(ctx){
 			var mesh = function(){};
-			var opt = ctx.opt;
+			var opt = ctx.opt || {};
+			opt.log = opt.log || console.log;
+			opt.gap = opt.gap || opt.wait || 1;
+			opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
 
 			mesh.out = function(msg){ var tmp;
 				if(this.to){ this.to.next(msg) }
@@ -1947,8 +1951,9 @@
 			mesh.hear = function(raw, peer){
 				if(!raw){ return }
 				var dup = ctx.dup, id, hash, msg, tmp = raw[0];
+				if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) }
 				try{msg = JSON.parse(raw);
-				}catch(e){console.log('DAM JSON parse error', e)}
+				}catch(e){opt.log('DAM JSON parse error', e)}
 				if('{' === tmp){
 					if(!msg){ return }
 					if(dup.check(id = msg['#'])){ return }
@@ -2012,20 +2017,25 @@
 					}
 					if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id]) && !o){ return } // TODO: still needs to be tested
 					if(peer.batch){
-						peer.batch.push(raw);
-						return;
+						peer.tail = (peer.tail || 0) + raw.length;
+						if(peer.tail <= opt.pack){
+							peer.batch.push(raw);
+							return;
+						}
+						flush(peer);
 					}
 					peer.batch = [];
-					setTimeout(function(){
-						var tmp = peer.batch;
-						if(!tmp){ return }
-						peer.batch = null;
-						if(!tmp.length){ return }
-						send(JSON.stringify(tmp), peer);
-					}, opt.gap || opt.wait || 1);
+					setTimeout(function(){flush(peer)}, opt.gap);
 					send(raw, peer);
 				}
-
+				function flush(peer){
+					var tmp = peer.batch;
+					if(!tmp){ return }
+					peer.batch = peer.tail = null;
+					if(!tmp.length){ return }
+					try{send(JSON.stringify(tmp), peer);
+					}catch(e){opt.log('DAM JSON stringify error', e)}
+				}
 				function send(raw, peer){
 					var wire = peer.wire;
 					try{
@@ -2111,6 +2121,7 @@
 				ctx.on('bye', peer);
 			}
 
+			mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) }
 			mesh.hear['?'] = function(msg, peer){
 				if(!msg.pid){ return mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer) }
 				peer.id = peer.id || msg.pid;
@@ -2162,7 +2173,7 @@
 
 			var wire = opt.wire;
 			opt.wire = open;
-			function open(peer){
+			function open(peer){ try{
 				if(!peer || !peer.url){ return wire && wire(peer) }
 				var url = peer.url.replace('http', 'ws');
 				var wire = peer.wire = new opt.WebSocket(url);
@@ -2185,7 +2196,7 @@
 					opt.mesh.hear(msg.data || msg, peer);
 				};
 				return wire;
-			}
+			}catch(e){}}
 
 			function reconnect(peer){
 				clearTimeout(peer.defer);

File diff suppressed because it is too large
+ 0 - 0
public/lib/gundb/gun.min.js


+ 1 - 1
public/lib/gundb/lib/evict.js

@@ -8,7 +8,7 @@
 		var util = process.memoryUsage;
 		if(!util){ return }
 		
-		ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 512) * 0.8;
+		ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 1399) * 0.8; // max_old_space_size defaults to 1400 MB. Note: old space !== memory space though.
 		
 		setInterval(check, 1000);
 		function check(){

+ 21 - 8
public/lib/gundb/lib/radisk.js

@@ -3,7 +3,9 @@
 	function Radisk(opt){
 
 		opt = opt || {};
+		opt.log = opt.log || console.log;
 		opt.file = String(opt.file || 'radata');
+		opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
 		opt.until = opt.until || opt.wait || 9;
 		opt.batch = opt.batch || 10 * 1000;
 		opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
@@ -11,19 +13,19 @@
 		opt.code.from = opt.code.from || '!';
 
 		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
-		map = Gun.obj.map;
+		var map = Gun.obj.map;
 
 		if(!opt.store){
-			return Gun.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");
+			return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");
 		}
 		if(!opt.store.put){
-			return Gun.log("ERROR: Radisk needs `store.put` interface with `(file, data, cb)`!");
+			return opt.log("ERROR: Radisk needs `store.put` interface with `(file, data, cb)`!");
 		}
 		if(!opt.store.get){
-			return Gun.log("ERROR: Radisk needs `store.get` interface with `(file, cb)`!");
+			return opt.log("ERROR: Radisk needs `store.get` interface with `(file, cb)`!");
 		}
 		if(!opt.store.list){
-			Gun.log("WARNING: `store.list` interface might be needed!");
+			//opt.log("WARNING: `store.list` interface might be needed!");
 		}
 
 		/*
@@ -73,7 +75,7 @@
 			r.batch.ed = 0;
 			r.save(batch, function(err, ok){
 				if(++i > 1){ return }
-				if(err){ Gun.log('err', err) }
+				if(err){ opt.log('err', err) }
 				map(batch.acks, function(cb){ cb(err, ok) });
 				thrash.at = null;
 				thrash.ing = false;
@@ -141,6 +143,7 @@
 			f.file = file;
 			f.each = function(val, key, k, pre){
 				if(u !== val){ f.count++ }
+				if(opt.pack <= (val||'').length){ return cb("Record too big!"), true }
 				var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : '='+ Radisk.encode(val)) +'\n';
 				if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !force){
 					f.text = '';
@@ -206,7 +209,7 @@
 					g.file = file;
 				}
 				g.it = function(err, disk){
-					if(g.err = err){ Gun.log('err', err) }
+					if(g.err = err){ opt.log('err', err) }
 					if(disk){ RAD = g.disk = disk }
 					disk = Q[g.file]; delete Q[g.file];
 					map(disk, g.ack);
@@ -243,6 +246,16 @@
 						//return cb(err, u);//map(q, p.ack);
 						return map(q, p.ack);
 					}
+					if(typeof data !== 'string'){
+						try{
+							if(opt.pack <= data.length){
+								p.err = "Chunk too big!";
+							} else {
+								data = data.toString();
+							}
+						}catch(e){ p.err = e }
+						if(p.err){ return map(q, p.ack) }
+					}
 					var tmp = p.split(data), pre = [], i, k, v;
 					while(tmp){
 						k = v = u;
@@ -309,7 +322,7 @@
 			}
 			r.list.init = function(err, disk){
 				if(err){
-					Gun.log('list', err);
+					opt.log('list', err);
 					setTimeout(function(){ r.parse(f, r.list.init) }, 1000);
 					return;
 				}

+ 1 - 1
public/lib/gundb/lib/radix.js

@@ -49,7 +49,7 @@
 		return radix;
 	};
 
-	Radix.map = function map(radix, cb, opt, pre){ pre = pre || []; 
+	Radix.map = function map(radix, cb, opt, pre){ pre = pre || [];
 		var t = radix[_] || radix, keys = radix.sort || (radix.sort = Object.keys(t).sort()), i = 0, l = keys.length;
 		for(;i < l; i++){ var key = keys[i], tree = t[key], tmp;
 			if(u !== (tmp = tree[$])){

+ 0 - 1
public/lib/gundb/lib/rfs.js

@@ -22,7 +22,6 @@ function Store(opt){
 				}
 				Gun.log("ERROR:", err)
 			}
-			if(data){ data = data.toString() }
 			cb(err, data);
 		});
 	};

+ 36 - 10
public/lib/gundb/lib/rindexed.js

@@ -34,11 +34,7 @@
 
     store.put = function(file, data, cb){
       cb = cb || function(){};
-      if(!db){
-        var es = 'ERROR: RAD IndexedDB not yet ready.'
-        console.log(es);
-        cb(es, undefined);
-      } else {
+      var doPut = function(){
         // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action.
         var transaction = db.transaction([opt.file], 'readwrite');
 
@@ -60,15 +56,20 @@
           cb(es, undefined);
         };
       }
+      if(!db){
+        waitDbReady(doGet, 100, function(){
+          var es = 'ERROR: Timeout: RAD IndexedDB not ready.';
+          console.log(es);
+          cb(es, undefined);
+        }, 10)
+      } else {
+        doPut();
+      }
     };
 
     store.get = function(file, cb){
       cb = cb || function(){};
-      if(!db){
-        var es = 'ERROR: RAD IndexedDB not yet ready.';
-        console.log(es);
-        cb(es, undefined);
-      } else {
+      var doGet = function(){
         // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action.
         var transaction = db.transaction([opt.file], 'readwrite');
 
@@ -90,6 +91,31 @@
           cb(es, undefined);
         };
       }
+      if(!db){
+        waitDbReady(doGet, 100, function(){
+          var es = 'ERROR: Timeout: RAD IndexedDB not ready.';
+          console.log(es);
+          cb(es, undefined);
+        }, 10)
+      } else {
+        doGet();
+      }
+    };
+
+    var waitDbReady = function(readyFunc, checkInterval, timeoutFunc, timeoutSecs){
+      var startTime = new Date();
+      var checkFunc = function(){
+        if(db){
+          readyFunc(); 
+        } else {
+          if((new Date() - startTime) / 1000 >= timeoutSecs){
+            timeoutFunc();
+          } else {
+            setTimeout(checkFunc, checkInterval);
+          }
+        }
+      };
+      checkFunc();
     };
 
     return store;

+ 1 - 1
public/lib/gundb/lib/rs3.js

@@ -62,7 +62,7 @@ function Store(opt){
 				if(!ack){ cb(null); return; }
 				cb(err, data);
 			};
-			if(data = (ack||{}).Body){ data = data.toString() }
+			data = (ack||{}).Body; //if(data = (ack||{}).Body){ data = data.toString() }
 			Gun.obj.map(cbs, cbe);
 		});
 	};

+ 3 - 2
public/lib/gundb/lib/serve.js

@@ -4,9 +4,10 @@ var path = require('path');
 function CDN(dir){
 	return function(req, res){
 		if(serve(req, res)){ return } // filters GUN requests!
-		fs.createReadStream(path.join(dir, req.url)).on('error',function(){ // static files!
+		fs.createReadStream(path.join(dir, req.url)).on('error',function(tmp){ // static files!
+			try{ tmp = fs.readFileSync(path.join(dir, 'index.html')) }catch(e){}
 			res.writeHead(200, {'Content-Type': 'text/html'});
-			res.end(fs.readFileSync(path.join(dir, 'index.html'))); // or default to index
+			res.end(tmp+''); // or default to index
 		}).pipe(res); // stream
 	}
 }

+ 1 - 1
public/lib/gundb/lib/store.js

@@ -41,7 +41,7 @@ Gun.on('create', function(root){
 		var id = msg['#'], soul = msg.get['#'], key = msg.get['.']||'', tmp = soul+'.'+key, node;
 		rad(tmp, function(err, val){
 			if(val){
-				Radix.map(val, each);
+				if(val && typeof val !== 'string'){ Radix.map(val, each) }
 				if(!node){ each(val, key) }
 			}
 			root.on('in', {'@': id, put: Gun.graph.node(node), err: err? err : u, rad: Radix});

+ 1 - 0
public/lib/gundb/lib/wire.js

@@ -65,6 +65,7 @@ Gun.on('opt', function(root){
 
 		opt.mesh = opt.mesh || Gun.Mesh(root);
 		ws.path = ws.path || '/gun';
+		ws.maxPayload = ws.maxPayload; // || opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3;
 		ws.web = new opt.WebSocket.Server(ws);
 		ws.web.on('connection', function(wire){ var peer;
 			wire.upgradeReq = wire.upgradeReq || {};

+ 107 - 85
public/lib/gundb/sea.js

@@ -6,8 +6,8 @@
   if(typeof global !== "undefined"){ root = global }
   root = root || {};
   var console = root.console || {log: function(){}};
-  function USE(arg){
-    return arg.slice? USE[R(arg)] : function(mod, path){
+  function USE(arg, req){
+    return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
       arg(mod = {exports: {}});
       USE[R(path)] = mod.exports;
     }
@@ -20,12 +20,24 @@
 
   ;USE(function(module){
     // Security, Encryption, and Authorization: SEA.js
-    // MANDATORY READING: http://gun.js.org/explainers/data/security.html
+    // MANDATORY READING: https://gun.eco/explainers/data/security.html
+    // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
     // THIS IS AN EARLY ALPHA!
 
-    function SEA(){}
-    if(typeof window !== "undefined"){ (SEA.window = window).SEA = SEA }
+    if(typeof window !== "undefined"){ module.window = window }
 
+    var tmp = module.window || module;
+    var SEA = tmp.SEA || function(){};
+
+    if(SEA.window = module.window){ try{
+      SEA.window.SEA = SEA;
+      tmp = document.createEvent('CustomEvent');
+      tmp.initCustomEvent('extension', false, false, {type: "SEA"});
+      (window.dispatchEvent || window.fireEvent)(tmp);
+      window.postMessage({type: "SEA"}, '*');
+    } catch(e){} }
+
+    try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
     module.exports = SEA;
   })(USE, './root');
 
@@ -148,7 +160,7 @@
     const Buffer = USE('./buffer')
     const api = {Buffer: Buffer}
 
-    if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') {
+    if (typeof window !== 'undefined') {
       var crypto = window.crypto || window.msCrypto;
       var subtle = crypto.subtle || crypto.webkitSubtle;
       const TextEncoder = window.TextEncoder
@@ -162,9 +174,9 @@
       })
     } else {
       try{
-        var crypto = require('crypto');
-        const { subtle } = require('@trust/webcrypto')             // All but ECDH
-        const { TextEncoder, TextDecoder } = require('text-encoding')
+        var crypto = USE('crypto', 1);
+        const { subtle } = USE('@trust/webcrypto', 1)             // All but ECDH
+        const { TextEncoder, TextDecoder } = USE('text-encoding', 1)
         Object.assign(api, {
           crypto,
           subtle,
@@ -173,7 +185,7 @@
           random: (len) => Buffer.from(crypto.randomBytes(len))
         });
         //try{
-          const WebCrypto = require('node-webcrypto-ossl')
+          const WebCrypto = USE('node-webcrypto-ossl', 1)
           api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
         //}catch(e){
           //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
@@ -189,6 +201,7 @@
   })(USE, './shim');
 
   ;USE(function(module){
+    const SEA = USE('./root');
     const Buffer = USE('./buffer')
     const settings = {}
     // Encryption parameters
@@ -225,6 +238,7 @@
       jwk: keysToEcdsaJwk,
       recall: authsettings
     })
+    SEA.opt = settings;
     module.exports = settings
   })(USE, './settings');
 
@@ -267,49 +281,40 @@
     var SEA = USE('./root');
     var shim = USE('./shim');
     var S = USE('./settings');
+    var sha = USE('./sha256');
     var u;
 
-    SEA.work = async (data, pair, cb) => { try { // used to be named `proof`
-      var salt = pair.epub || pair; // epub not recommended, salt should be random!
+    SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof`
+      var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random!
+      var opt = opt || {};
       if(salt instanceof Function){
         cb = salt;
         salt = u;
       }
       salt = salt || shim.random(9);
-      if (SEA.window) {
-        // For browser subtle works fine
-        const key = await shim.subtle.importKey(
-          'raw', new shim.TextEncoder().encode(data), { name: 'PBKDF2' }, false, ['deriveBits']
-        )
-        const result = await shim.subtle.deriveBits({
-          name: 'PBKDF2',
-          iterations: S.pbkdf2.iter,
-          salt: new shim.TextEncoder().encode(salt),
-          hash: S.pbkdf2.hash,
-        }, key, S.pbkdf2.ks * 8)
-        data = shim.random(data.length)  // Erase data in case of passphrase
-        const r = shim.Buffer.from(result, 'binary').toString('utf8')
-        if(cb){ try{ cb(r) }catch(e){console.log(e)} }
-        return r;
+      if('SHA-256' === opt.name){
+        var rsha = shim.Buffer.from(await sha(data), 'binary').toString('utf8')
+        if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
+        return rsha;
       }
-      // For NodeJS crypto.pkdf2 rocks
-      const crypto = shim.crypto;
-      const hash = crypto.pbkdf2Sync(
-        data,
-        new shim.TextEncoder().encode(salt),
-        S.pbkdf2.iter,
-        S.pbkdf2.ks,
-        S.pbkdf2.hash.replace('-', '').toLowerCase()
+      const key = await (shim.ossl || shim.subtle).importKey(
+        'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits']
       )
-      data = shim.random(data.length)  // Erase passphrase for app
-      const r = hash && hash.toString('utf8')
+      const result = await (shim.ossl || shim.subtle).deriveBits({
+        name: opt.name || 'PBKDF2',
+        iterations: opt.iterations || S.pbkdf2.iter,
+        salt: new shim.TextEncoder().encode(opt.salt || salt),
+        hash: opt.hash || S.pbkdf2.hash,
+      }, key, opt.length || (S.pbkdf2.ks * 8))
+      data = shim.random(data.length)  // Erase data in case of passphrase
+      const r = shim.Buffer.from(result, 'binary').toString('utf8')
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.work;
   })(USE, './work');
@@ -321,7 +326,7 @@
     var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer;
 
     //SEA.pair = async (data, proof, cb) => { try {
-    SEA.pair = async (cb) => { try {
+    SEA.pair = SEA.pair || (async (cb) => { try {
 
       const ecdhSubtle = shim.ossl || shim.subtle
       // First: ECDSA keys for signing/verifying...
@@ -372,7 +377,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.pair;
   })(USE, './pair');
@@ -383,7 +388,7 @@
     var S = USE('./settings');
     var sha256hash = USE('./sha256');
 
-    SEA.sign = async (data, pair, cb) => { try {
+    SEA.sign = SEA.sign || (async (data, pair, cb) => { try {
       if(data && data.slice
       && 'SEA{' === data.slice(0,4)
       && '"m":' === data.slice(4,8)){
@@ -409,7 +414,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.sign;
   })(USE, './sign');
@@ -422,7 +427,7 @@
     var parse = USE('./parse');
     var u;
 
-    SEA.verify = async (data, pair, cb) => { try {
+    SEA.verify = SEA.verify || (async (data, pair, cb) => { try {
       const json = parse(data)
       if(false === pair){ // don't verify!
         const raw = (json !== data)? 
@@ -447,7 +452,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.verify;
   })(USE, './verify');
@@ -472,13 +477,13 @@
     var S = USE('./settings');
     var aeskey = USE('./aeskey');
 
-    SEA.encrypt = async (data, pair, cb, opt) => { try {
+    SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const msg = JSON.stringify(data)
       const rand = {s: shim.random(8), iv: shim.random(16)};
       const ct = await aeskey(key, rand.s, opt)
-      .then((aes) => shim.subtle.encrypt({ // Keeping the AES key scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
       }, aes, new shim.TextEncoder().encode(msg)))
       const r = 'SEA'+JSON.stringify({
@@ -493,7 +498,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.encrypt;
   })(USE, './encrypt');
@@ -505,23 +510,22 @@
     var aeskey = USE('./aeskey');
     var parse = USE('./parse');
 
-    SEA.decrypt = async (data, pair, cb, opt) => { try {
+    SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const json = parse(data)
       const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
-      .then((aes) => shim.subtle.decrypt({  // Keeping aesKey scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({  // Keeping aesKey scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8'))
       }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8'))))
       const r = parse(new shim.TextDecoder('utf8').decode(ct))
-      
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.decrypt;
   })(USE, './decrypt');
@@ -531,7 +535,7 @@
     var shim = USE('./shim');
     var S = USE('./settings');
     // Derive shared secret from other's pub and my epub/epriv
-    SEA.secret = async (key, pair, cb) => { try {
+    SEA.secret = SEA.secret || (async (key, pair, cb) => { try {
       const pub = key.epub || key
       const epub = pair.epub
       const epriv = pair.epriv
@@ -555,7 +559,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     const keysToEcdhJwk = (pub, d) => { // d === priv
       //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old
@@ -605,7 +609,7 @@
     SEA.encrypt = USE('./encrypt');
     SEA.decrypt = USE('./decrypt');
 
-    SEA.random = getRandomBytes;
+    SEA.random = SEA.random || getRandomBytes;
 
     // This is easy way to use IndexedDB, all methods are Promises
     // Note: Not all SEA interfaces have to support this.
@@ -613,7 +617,7 @@
 
     // This is Buffer used in SEA and usable from Gun/SEA application also.
     // For documentation see https://nodejs.org/api/buffer.html
-    SEA.Buffer = Buffer;
+    SEA.Buffer = SEA.Buffer || Buffer;
 
     // These SEA functions support now ony Promises or
     // async/await (compatible) code, use those like Promises.
@@ -621,7 +625,7 @@
     // Creates a wrapper library around Web Crypto API
     // for various AES, ECDSA, PBKDF2 functions we called above.
     // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
-    SEA.keyid = async (pub) => {
+    SEA.keyid = SEA.keyid || (async (pub) => {
       try {
         // base64('base64(x):base64(y)') => Buffer(xy)
         const pb = Buffer.concat(
@@ -639,7 +643,7 @@
         console.log(e)
         throw e
       }
-    }
+    });
     // all done!
     // Obviously it is missing MANY necessary features. This is only an alpha release.
     // Please experiment with it, audit what I've done so far, and complain about what needs to be added.
@@ -649,7 +653,7 @@
     // But all other behavior needs to be equally easy, like opinionated ways of
     // Adding friends (trusted public keys), sending private messages, etc.
     // Cheers! Tell me what you think.
-    var Gun = (SEA.window||{}).Gun || require('./gun');
+    var Gun = (SEA.window||{}).Gun || USE('./gun', 1);
     Gun.SEA = SEA;
     SEA.Gun = Gun;
 
@@ -662,9 +666,9 @@
     // This is internal func queries public key(s) for alias.
     const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
       // load all public keys associated with the username alias we want to log in with.
-      gunRoot.get('~@'+alias).get((rat, rev) => {
-        rev.off();
-        if (!rat.put) {
+      gunRoot.get('~@'+alias).once((data, key) => {
+        //rev.off();
+        if (!data) {
           // if no user, don't do anything.
           const err = 'No user!'
           Gun.log(err)
@@ -674,19 +678,18 @@
         const aliases = []
         let c = 0
         // TODO: how about having real chainable map without callback ?
-        Gun.obj.map(rat.put, (at, pub) => {
+        Gun.obj.map(data, (at, pub) => {
           if (!pub.slice || '~' !== pub.slice(0, 1)) {
             // TODO: ... this would then be .filter((at, pub))
             return
           }
           ++c
           // grab the account associated with this public key.
-          gunRoot.get(pub).get((at, ev) => {
+          gunRoot.get(pub).once(data => {
             pub = pub.slice(1)
-            ev.off()
             --c
-            if (at.put){
-              aliases.push({ pub, at })
+            if (data){
+              aliases.push({ pub, put: data })
             }
             if (!c && (c = -1)) {
               resolve(aliases)
@@ -710,7 +713,7 @@
     const authenticate = async (alias, pass, gunRoot) => {
       // load all public keys associated with the username alias we want to log in with.
       const aliases = (await queryGunAliases(alias, gunRoot))
-      .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put)
+      .filter(a => !!a.pub && !!a.put)
       // Got any?
       if (!aliases.length) {
         throw { err: 'Public key does not exist!' }
@@ -718,14 +721,14 @@
       let err
       // then attempt to log into each one until we find ours!
       // (if two users have the same username AND the same password... that would be bad)
-      const users = await Promise.all(aliases.map(async ({ at: at, pub: pub }, i) => {
+      const users = await Promise.all(aliases.map(async (a, i) => {
         // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
-        const auth = parseProps(at.put.auth)
+        const auth = parseProps(a.put.auth)
       // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
       // SEA.verify(at.put.auth, pub).then(function(auth){
         try {
           const proof = await SEA.work(pass, auth.s)
-          const props = { pub: pub, proof: proof, at: at }
+          //const props = { pub: pub, proof: proof, at: at }
           // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
           /*
           MARK TO @mhelander : pub vs epub!???
@@ -733,24 +736,24 @@
           const salt = auth.salt
           const sea = await SEA.decrypt(auth.ek, proof)
           if (!sea) {
-            err = 'Failed to decrypt secret! ' + i +'/'+aliases.length;
+            err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
             return
           }
           // now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
           // if we were successful, then that meanswe're logged in!
           const priv = sea.priv
           const epriv = sea.epriv
-          const epub = at.put.epub
+          const epub = a.put.epub
           // TODO: 'salt' needed?
           err = null
-          if(typeof window !== 'undefined'){
-            var tmp = window.sessionStorage;
+          if(SEA.window){
+            var tmp = SEA.window.sessionStorage;
             if(tmp && gunRoot._.opt.remember){
-              window.sessionStorage.alias = alias;
-              window.sessionStorage.tmp = pass;
+              SEA.window.sessionStorage.alias = alias;
+              SEA.window.sessionStorage.tmp = pass;
             }
           }
-          return Object.assign(props, { priv: priv, salt: salt, epub: epub, epriv: epriv })
+          return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
         } catch (e) {
           err = 'Failed to decrypt secret!'
           throw { err }
@@ -862,10 +865,25 @@
     const finalizeLogin = async (alias, key, gunRoot, opts) => {
       const user = gunRoot._.user
       // add our credentials in-memory only to our root gun instance
-      //var tmp = user._.tag;
+      var tmp = user._.tag;
       var opt = user._.opt;
-      user._ = key.at.$._;
+      user._ = gunRoot.get('~'+key.pub)._;
       user._.opt = opt;
+      var tags = user._.tag;
+      /*Object.values && Object.values(tmp).forEach(function(tag){
+        // TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
+        var t = tags[tag.tag];
+        console.log("hm??", tag, t);
+        if(!t){
+          tags[tag.tag] = tag;
+          return;
+        }
+        if(tag.last){
+          tag.last.to = t.to;
+          t.last = tag.last = t.last || tag.last;
+        }
+        t.to = tag.to;
+      })*/
       //user._.tag = tmp || user._.tag;
       // so that way we can use the credentials to encrypt/decrypt data
       // that is input/output through gun (see below)
@@ -880,7 +898,8 @@
       //await authPersist(user._, key.proof, opts) // temporarily disabled
       // emit an auth event, useful for page redirects and stuff.  
       try {
-        gunRoot._.on('auth', user._)
+        gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
+        //user._.on('auth', user._) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
       } catch (e) {
         console.log('Your \'auth\' callback crashed with:', e)
       }
@@ -1300,7 +1319,8 @@
     }
     // If authentication is to be remembered over reloads or browser closing,
     // set validity time in minutes.
-    User.prototype.recall = async function(setvalidity, options){ 
+    User.prototype.recall = function(setvalidity, options){
+      var gun = this;
       const gunRoot = this.back(-1)
 
       let validity
@@ -1317,7 +1337,7 @@
             }
           }
         }
-        return this;
+        return gun;
       }
 
       if (!Gun.val.is(setvalidity)) {
@@ -1340,13 +1360,15 @@
         authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
         ? opts.hook : _initial_authsettings.hook
         // All is good. Should we do something more with actual recalled data?
-        return await authRecall(gunRoot)
+        (async function(){ await authRecall(gunRoot) }());
+        return gun;
       } catch (e) {
         const err = 'No session!'
         Gun.log(err)
         // NOTE! It's fine to resolve recall with reason why not successful
         // instead of rejecting...
-        return { err: (e && e.err) || err }
+        //return { err: (e && e.err) || err }
+        return gun;
       }
     }
     User.prototype.alive = async function(){
@@ -1563,7 +1585,7 @@
             if(tmp = relpub(soul)){
               check['any'+soul+key] = 1;
               SEA.verify(val, pub = tmp, function(data){ var rel;
-                if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) }
+                if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
                 if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
                   (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
                 }

+ 11 - 11
public/lib/gundb/sea/authenticate.js

@@ -7,7 +7,7 @@
     const authenticate = async (alias, pass, gunRoot) => {
       // load all public keys associated with the username alias we want to log in with.
       const aliases = (await queryGunAliases(alias, gunRoot))
-      .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put)
+      .filter(a => !!a.pub && !!a.put)
       // Got any?
       if (!aliases.length) {
         throw { err: 'Public key does not exist!' }
@@ -15,14 +15,14 @@
       let err
       // then attempt to log into each one until we find ours!
       // (if two users have the same username AND the same password... that would be bad)
-      const users = await Promise.all(aliases.map(async ({ at: at, pub: pub }, i) => {
+      const users = await Promise.all(aliases.map(async (a, i) => {
         // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
-        const auth = parseProps(at.put.auth)
+        const auth = parseProps(a.put.auth)
       // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
       // SEA.verify(at.put.auth, pub).then(function(auth){
         try {
           const proof = await SEA.work(pass, auth.s)
-          const props = { pub: pub, proof: proof, at: at }
+          //const props = { pub: pub, proof: proof, at: at }
           // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
           /*
           MARK TO @mhelander : pub vs epub!???
@@ -30,24 +30,24 @@
           const salt = auth.salt
           const sea = await SEA.decrypt(auth.ek, proof)
           if (!sea) {
-            err = 'Failed to decrypt secret! ' + i +'/'+aliases.length;
+            err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
             return
           }
           // now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
           // if we were successful, then that meanswe're logged in!
           const priv = sea.priv
           const epriv = sea.epriv
-          const epub = at.put.epub
+          const epub = a.put.epub
           // TODO: 'salt' needed?
           err = null
-          if(typeof window !== 'undefined'){
-            var tmp = window.sessionStorage;
+          if(SEA.window){
+            var tmp = SEA.window.sessionStorage;
             if(tmp && gunRoot._.opt.remember){
-              window.sessionStorage.alias = alias;
-              window.sessionStorage.tmp = pass;
+              SEA.window.sessionStorage.alias = alias;
+              SEA.window.sessionStorage.tmp = pass;
             }
           }
-          return Object.assign(props, { priv: priv, salt: salt, epub: epub, epriv: epriv })
+          return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
         } catch (e) {
           err = 'Failed to decrypt secret!'
           throw { err }

+ 7 - 4
public/lib/gundb/sea/create.js

@@ -202,7 +202,8 @@
     }
     // If authentication is to be remembered over reloads or browser closing,
     // set validity time in minutes.
-    User.prototype.recall = async function(setvalidity, options){ 
+    User.prototype.recall = function(setvalidity, options){
+      var gun = this;
       const gunRoot = this.back(-1)
 
       let validity
@@ -219,7 +220,7 @@
             }
           }
         }
-        return this;
+        return gun;
       }
 
       if (!Gun.val.is(setvalidity)) {
@@ -242,13 +243,15 @@
         authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
         ? opts.hook : _initial_authsettings.hook
         // All is good. Should we do something more with actual recalled data?
-        return await authRecall(gunRoot)
+        (async function(){ await authRecall(gunRoot) }());
+        return gun;
       } catch (e) {
         const err = 'No session!'
         Gun.log(err)
         // NOTE! It's fine to resolve recall with reason why not successful
         // instead of rejecting...
-        return { err: (e && e.err) || err }
+        //return { err: (e && e.err) || err }
+        return gun;
       }
     }
     User.prototype.alive = async function(){

+ 3 - 4
public/lib/gundb/sea/decrypt.js

@@ -5,23 +5,22 @@
     var aeskey = require('./aeskey');
     var parse = require('./parse');
 
-    SEA.decrypt = async (data, pair, cb, opt) => { try {
+    SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const json = parse(data)
       const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
-      .then((aes) => shim.subtle.decrypt({  // Keeping aesKey scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({  // Keeping aesKey scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8'))
       }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8'))))
       const r = parse(new shim.TextDecoder('utf8').decode(ct))
-      
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.decrypt;
   

+ 3 - 3
public/lib/gundb/sea/encrypt.js

@@ -4,13 +4,13 @@
     var S = require('./settings');
     var aeskey = require('./aeskey');
 
-    SEA.encrypt = async (data, pair, cb, opt) => { try {
+    SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const msg = JSON.stringify(data)
       const rand = {s: shim.random(8), iv: shim.random(16)};
       const ct = await aeskey(key, rand.s, opt)
-      .then((aes) => shim.subtle.encrypt({ // Keeping the AES key scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
       }, aes, new shim.TextEncoder().encode(msg)))
       const r = 'SEA'+JSON.stringify({
@@ -25,7 +25,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.encrypt;
   

+ 2 - 1
public/lib/gundb/sea/index.js

@@ -62,6 +62,7 @@
         // if there is a request to read data from us, then...
         var soul = msg.get['#'];
         if(soul){ // for now, only allow direct IDs to be read.
+          if(soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
           if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
             return to.next(msg); // yes.
           } else
@@ -149,7 +150,7 @@
             if(tmp = relpub(soul)){
               check['any'+soul+key] = 1;
               SEA.verify(val, pub = tmp, function(data){ var rel;
-                if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) }
+                if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
                 if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
                   (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
                 }

+ 19 - 3
public/lib/gundb/sea/login.js

@@ -4,10 +4,25 @@
     const finalizeLogin = async (alias, key, gunRoot, opts) => {
       const user = gunRoot._.user
       // add our credentials in-memory only to our root gun instance
-      //var tmp = user._.tag;
+      var tmp = user._.tag;
       var opt = user._.opt;
-      user._ = key.at.$._;
+      user._ = gunRoot.get('~'+key.pub)._;
       user._.opt = opt;
+      var tags = user._.tag;
+      /*Object.values && Object.values(tmp).forEach(function(tag){
+        // TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
+        var t = tags[tag.tag];
+        console.log("hm??", tag, t);
+        if(!t){
+          tags[tag.tag] = tag;
+          return;
+        }
+        if(tag.last){
+          tag.last.to = t.to;
+          t.last = tag.last = t.last || tag.last;
+        }
+        t.to = tag.to;
+      })*/
       //user._.tag = tmp || user._.tag;
       // so that way we can use the credentials to encrypt/decrypt data
       // that is input/output through gun (see below)
@@ -22,7 +37,8 @@
       //await authPersist(user._, key.proof, opts) // temporarily disabled
       // emit an auth event, useful for page redirects and stuff.  
       try {
-        gunRoot._.on('auth', user._)
+        gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
+        //user._.on('auth', user._) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
       } catch (e) {
         console.log('Your \'auth\' callback crashed with:', e)
       }

+ 2 - 2
public/lib/gundb/sea/pair.js

@@ -5,7 +5,7 @@
     var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer;
 
     //SEA.pair = async (data, proof, cb) => { try {
-    SEA.pair = async (cb) => { try {
+    SEA.pair = SEA.pair || (async (cb) => { try {
 
       const ecdhSubtle = shim.ossl || shim.subtle
       // First: ECDSA keys for signing/verifying...
@@ -56,7 +56,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.pair;
   

+ 7 - 8
public/lib/gundb/sea/query.js

@@ -4,9 +4,9 @@
     // This is internal func queries public key(s) for alias.
     const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
       // load all public keys associated with the username alias we want to log in with.
-      gunRoot.get('~@'+alias).get((rat, rev) => {
-        rev.off();
-        if (!rat.put) {
+      gunRoot.get('~@'+alias).once((data, key) => {
+        //rev.off();
+        if (!data) {
           // if no user, don't do anything.
           const err = 'No user!'
           Gun.log(err)
@@ -16,19 +16,18 @@
         const aliases = []
         let c = 0
         // TODO: how about having real chainable map without callback ?
-        Gun.obj.map(rat.put, (at, pub) => {
+        Gun.obj.map(data, (at, pub) => {
           if (!pub.slice || '~' !== pub.slice(0, 1)) {
             // TODO: ... this would then be .filter((at, pub))
             return
           }
           ++c
           // grab the account associated with this public key.
-          gunRoot.get(pub).get((at, ev) => {
+          gunRoot.get(pub).once(data => {
             pub = pub.slice(1)
-            ev.off()
             --c
-            if (at.put){
-              aliases.push({ pub, at })
+            if (data){
+              aliases.push({ pub, put: data })
             }
             if (!c && (c = -1)) {
               resolve(aliases)

+ 15 - 3
public/lib/gundb/sea/root.js

@@ -1,10 +1,22 @@
 
     // Security, Encryption, and Authorization: SEA.js
-    // MANDATORY READING: http://gun.js.org/explainers/data/security.html
+    // MANDATORY READING: https://gun.eco/explainers/data/security.html
+    // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
     // THIS IS AN EARLY ALPHA!
 
-    function SEA(){}
-    if(typeof window !== "undefined"){ (SEA.window = window).SEA = SEA }
+    if(typeof window !== "undefined"){ module.window = window }
 
+    var tmp = module.window || module;
+    var SEA = tmp.SEA || function(){};
+
+    if(SEA.window = module.window){ try{
+      SEA.window.SEA = SEA;
+      tmp = document.createEvent('CustomEvent');
+      tmp.initCustomEvent('extension', false, false, {type: "SEA"});
+      (window.dispatchEvent || window.fireEvent)(tmp);
+      window.postMessage({type: "SEA"}, '*');
+    } catch(e){} }
+
+    try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
     module.exports = SEA;
   

+ 5 - 5
public/lib/gundb/sea/sea.js

@@ -29,7 +29,7 @@
     SEA.encrypt = require('./encrypt');
     SEA.decrypt = require('./decrypt');
 
-    SEA.random = getRandomBytes;
+    SEA.random = SEA.random || getRandomBytes;
 
     // This is easy way to use IndexedDB, all methods are Promises
     // Note: Not all SEA interfaces have to support this.
@@ -37,7 +37,7 @@
 
     // This is Buffer used in SEA and usable from Gun/SEA application also.
     // For documentation see https://nodejs.org/api/buffer.html
-    SEA.Buffer = Buffer;
+    SEA.Buffer = SEA.Buffer || Buffer;
 
     // These SEA functions support now ony Promises or
     // async/await (compatible) code, use those like Promises.
@@ -45,7 +45,7 @@
     // Creates a wrapper library around Web Crypto API
     // for various AES, ECDSA, PBKDF2 functions we called above.
     // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
-    SEA.keyid = async (pub) => {
+    SEA.keyid = SEA.keyid || (async (pub) => {
       try {
         // base64('base64(x):base64(y)') => Buffer(xy)
         const pb = Buffer.concat(
@@ -63,7 +63,7 @@
         console.log(e)
         throw e
       }
-    }
+    });
     // all done!
     // Obviously it is missing MANY necessary features. This is only an alpha release.
     // Please experiment with it, audit what I've done so far, and complain about what needs to be added.
@@ -73,7 +73,7 @@
     // But all other behavior needs to be equally easy, like opinionated ways of
     // Adding friends (trusted public keys), sending private messages, etc.
     // Cheers! Tell me what you think.
-    var Gun = (SEA.window||{}).Gun || require('./gun');
+    var Gun = (SEA.window||{}).Gun || require('./gun', 1);
     Gun.SEA = SEA;
     SEA.Gun = Gun;
 

+ 2 - 2
public/lib/gundb/sea/secret.js

@@ -3,7 +3,7 @@
     var shim = require('./shim');
     var S = require('./settings');
     // Derive shared secret from other's pub and my epub/epriv
-    SEA.secret = async (key, pair, cb) => { try {
+    SEA.secret = SEA.secret || (async (key, pair, cb) => { try {
       const pub = key.epub || key
       const epub = pair.epub
       const epriv = pair.epriv
@@ -27,7 +27,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     const keysToEcdhJwk = (pub, d) => { // d === priv
       //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old

+ 2 - 0
public/lib/gundb/sea/settings.js

@@ -1,4 +1,5 @@
 
+    const SEA = require('./root');
     const Buffer = require('./buffer')
     const settings = {}
     // Encryption parameters
@@ -35,5 +36,6 @@
       jwk: keysToEcdsaJwk,
       recall: authsettings
     })
+    SEA.opt = settings;
     module.exports = settings
   

+ 5 - 5
public/lib/gundb/sea/shim.js

@@ -2,7 +2,7 @@
     const Buffer = require('./buffer')
     const api = {Buffer: Buffer}
 
-    if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') {
+    if (typeof window !== 'undefined') {
       var crypto = window.crypto || window.msCrypto;
       var subtle = crypto.subtle || crypto.webkitSubtle;
       const TextEncoder = window.TextEncoder
@@ -16,9 +16,9 @@
       })
     } else {
       try{
-        var crypto = require('crypto');
-        const { subtle } = require('@trust/webcrypto')             // All but ECDH
-        const { TextEncoder, TextDecoder } = require('text-encoding')
+        var crypto = require('crypto', 1);
+        const { subtle } = require('@trust/webcrypto', 1)             // All but ECDH
+        const { TextEncoder, TextDecoder } = require('text-encoding', 1)
         Object.assign(api, {
           crypto,
           subtle,
@@ -27,7 +27,7 @@
           random: (len) => Buffer.from(crypto.randomBytes(len))
         });
         //try{
-          const WebCrypto = require('node-webcrypto-ossl')
+          const WebCrypto = require('node-webcrypto-ossl', 1)
           api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
         //}catch(e){
           //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");

+ 5 - 5
public/lib/gundb/sea/sign.js

@@ -4,8 +4,8 @@
     var S = require('./settings');
     var sha256hash = require('./sha256');
 
-    SEA.sign = async (data, pair, cb) => { try {
-      if(data.slice
+    SEA.sign = SEA.sign || (async (data, pair, cb) => { try {
+      if(data && data.slice
       && 'SEA{' === data.slice(0,4)
       && '"m":' === data.slice(4,8)){
         // TODO: This would prevent pair2 signing pair1's signature.
@@ -19,8 +19,8 @@
       const jwk = S.jwk(pub, priv)
       const msg = JSON.stringify(data)
       const hash = await sha256hash(msg)
-      const sig = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
-      .then((key) => shim.subtle.sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
+      const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
+      .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
       const r = 'SEA'+JSON.stringify({m: msg, s: shim.Buffer.from(sig, 'binary').toString('utf8')});
 
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
@@ -30,7 +30,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.sign;
   

+ 2 - 2
public/lib/gundb/sea/verify.js

@@ -6,7 +6,7 @@
     var parse = require('./parse');
     var u;
 
-    SEA.verify = async (data, pair, cb) => { try {
+    SEA.verify = SEA.verify || (async (data, pair, cb) => { try {
       const json = parse(data)
       if(false === pair){ // don't verify!
         const raw = (json !== data)? 
@@ -31,7 +31,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.verify;
   

+ 19 - 28
public/lib/gundb/sea/work.js

@@ -2,49 +2,40 @@
     var SEA = require('./root');
     var shim = require('./shim');
     var S = require('./settings');
+    var sha = require('./sha256');
     var u;
 
-    SEA.work = async (data, pair, cb) => { try { // used to be named `proof`
-      var salt = pair.epub || pair; // epub not recommended, salt should be random!
+    SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof`
+      var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random!
+      var opt = opt || {};
       if(salt instanceof Function){
         cb = salt;
         salt = u;
       }
       salt = salt || shim.random(9);
-      if (SEA.window) {
-        // For browser subtle works fine
-        const key = await shim.subtle.importKey(
-          'raw', new shim.TextEncoder().encode(data), { name: 'PBKDF2' }, false, ['deriveBits']
-        )
-        const result = await shim.subtle.deriveBits({
-          name: 'PBKDF2',
-          iterations: S.pbkdf2.iter,
-          salt: new shim.TextEncoder().encode(salt),
-          hash: S.pbkdf2.hash,
-        }, key, S.pbkdf2.ks * 8)
-        data = shim.random(data.length)  // Erase data in case of passphrase
-        const r = shim.Buffer.from(result, 'binary').toString('utf8')
-        if(cb){ try{ cb(r) }catch(e){console.log(e)} }
-        return r;
+      if('SHA-256' === opt.name){
+        var rsha = shim.Buffer.from(await sha(data), 'binary').toString('utf8')
+        if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
+        return rsha;
       }
-      // For NodeJS crypto.pkdf2 rocks
-      const crypto = shim.crypto;
-      const hash = crypto.pbkdf2Sync(
-        data,
-        new shim.TextEncoder().encode(salt),
-        S.pbkdf2.iter,
-        S.pbkdf2.ks,
-        S.pbkdf2.hash.replace('-', '').toLowerCase()
+      const key = await (shim.ossl || shim.subtle).importKey(
+        'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits']
       )
-      data = shim.random(data.length)  // Erase passphrase for app
-      const r = hash && hash.toString('utf8')
+      const result = await (shim.ossl || shim.subtle).deriveBits({
+        name: opt.name || 'PBKDF2',
+        iterations: opt.iterations || S.pbkdf2.iter,
+        salt: new shim.TextEncoder().encode(opt.salt || salt),
+        hash: opt.hash || S.pbkdf2.hash,
+      }, key, opt.length || (S.pbkdf2.ks * 8))
+      data = shim.random(data.length)  // Erase data in case of passphrase
+      const r = shim.Buffer.from(result, 'binary').toString('utf8')
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.work;
   

+ 44 - 0
public/web/header.js

@@ -0,0 +1,44 @@
+import page from '/lib/page.mjs';
+
+class Header {
+    constructor() {
+        console.log("app constructor");
+        this.language = _LangManager.language;
+
+    }
+
+    init(){
+
+
+        let el = document.createElement("div");
+        el.setAttribute("id", "header");
+        document.body.appendChild(el);
+
+        let headerGUI = {
+            
+                $type: "a",
+                class: "mdc-button mdc-button--compact mdc-card__action",
+                $text: "Back",
+                //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
+                onclick: function (e) {
+                    window.history.back();
+                }
+            
+        }
+
+
+        document.querySelector("#header").$cell({
+            id: 'header',
+            $cell: true,
+            $type: "div",
+            $components: [
+                headerGUI
+            ]
+        })
+
+    }
+
+
+}
+
+export { Header }

+ 4 - 0
public/web/index-app.css

@@ -76,4 +76,8 @@ background: rgb(217, 217, 217);
 
 .divider {
   border-bottom-width: thin;
+}
+
+.actionButton {
+  margin: 3px
 }

+ 456 - 486
public/web/index-app.js

@@ -1,8 +1,11 @@
 import page from '/lib/page.mjs';
+import { Header } from '/web/header.js';
+
 
 class IndexApp {
     constructor() {
         console.log("app constructor");
+        
 
         this.worlds = {};
         this.language = _LangManager.language;
@@ -21,9 +24,9 @@ class IndexApp {
         var socket = io.connect(window._app.reflectorHost, this.options);
 
         const parse = (msg) => {
-            this.parseAppInstancesData(msg)
+            this.parseOnlineData(msg)
         }
-        socket.on('getWebAppUpdate', msg => parse.call(self, msg));
+        socket.on('getWebAppUpdate', msg => parse.call(this, msg));
         socket.on("connect", function () {
 
             let noty = new Noty({
@@ -54,12 +57,63 @@ class IndexApp {
 
     }
 
+    initHTML() {
+
+        let self = this;
+
+        //first init from _app
+        document.querySelector('head').innerHTML += '<link rel="stylesheet" href="/web/index-app.css">';
+
+        let headerGUI = new Header();
+        headerGUI.init();
+
+        //add HTML
+        let entry = document.createElement("div");
+        entry.setAttribute("id", 'app');
+        document.body.appendChild(entry);
+
+        let divs = ['appGUI', 'userLobby', 'main', 'worldsGUI'];
+        divs.forEach(el=>{
+            let appEl = document.createElement("div");
+            appEl.setAttribute("id", el);
+            entry.appendChild(appEl);
+        })
+        
+        //init CELL
+        document.querySelector("#userLobby").$cell({
+            id: "userLobby",
+            $cell: true,
+            $type: "div",
+            $components: [],
+            $update: function(){
+                this.$components = self.initUserGUI()
+            }
+        });
+
+        document.querySelector("#worldsGUI").$cell({
+            id: 'worldsGUI',
+            $cell: true,
+            $type: "div",
+            $components: [],
+            _comps: [],
+            _refresh: async function(data, fn){
+                _app.showProgressBar();
+                this._comps = await fn.call(self, data);
+                this.$update();
+                _app.hideProgressBar();
+            },
+            $update: async function(){
+                this.$components = this._comps
+            }
+        });
 
 
+    }
+
     async generateFrontPage() {
 
         let infoEl = document.createElement("div");
-        infoEl.setAttribute("id", "info");
+        infoEl.setAttribute("id", "indexPage");
 
         let lang = _LangManager.locale;
 
@@ -79,22 +133,122 @@ class IndexApp {
 
     }
 
-    async initApp() {
-
-        let appEl = document.createElement("div");
-        appEl.setAttribute("id", "app");
+   initApp() {
 
-        let appElHTML = await _app.helpers.getHtmlText('/web/app.html');
-        appEl.innerHTML = appElHTML;
-        document.body.appendChild(appEl);
+        // let appElHTML = await _app.helpers.getHtmlText('/web/app.html');
+        // appEl.innerHTML = appElHTML;
+        // document.body.appendChild(appEl);
 
         this.initUser();
-        this.initUserGUI();
-        this.initWorldsListGUI();
+        document.querySelector("#userLobby").$update();
+
+        //this.initWorldsListGUI();
         //this.getAppDetailsFromDB();
 
     }
 
+    async initWorldsProtosListForUser (userAlias) {
+        document.querySelector("#worldsGUI").$components = [];
+        await document.querySelector("#worldsGUI")._refresh(userAlias, this.getWorldsProtosListForUser);
+     }
+
+    async initWorldsStatesListForUser (userAlias) {
+        document.querySelector("#worldsGUI").$components = [];
+        await document.querySelector("#worldsGUI")._refresh(userAlias, this.getWorldsStatesListForUser);
+     }
+
+
+    async getWorldsStatesListForUser (userAlias) {
+
+        let worldsGUI = [];
+
+        let data = await _app.getAllStateWorldsInfoForUser(userAlias);
+
+        Object.entries(data).forEach(el=>{
+
+            let worlds = this.createWorldsGUI(userAlias, el[0]);
+            worlds._states = el[1];
+            worlds.$update();
+            worldsGUI.push(worlds);
+
+        })
+
+       
+        return [ 
+                {
+                    $type: "div",
+                    class: "mdc-layout-grid",
+                    $components: [
+                        {
+                            $type: "div",
+                            class: "mdc-layout-grid__inner",
+                            $components: [
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                        {
+                                            $type: "h1",
+                                            class: "mdc-typography--headline4",
+                                            $text: 'States for ' + userAlias
+                                        }
+                                    ]
+                                },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [].concat(worldsGUI)
+                                }
+                            ]
+                        }
+                    ]
+                }
+            ]
+    }
+
+    async getWorldsProtosListForUser (userAlias) {
+
+
+            let worldsGUI = [];
+
+            let data = await _app.getAllProtoWorldsInfoForUser(userAlias);
+
+            let worlds = this.createWorldsGUI(userAlias);
+            worlds._states = data;
+            worlds.$update();
+            worldsGUI.push(worlds);
+        
+            return [
+                    {
+                        $type: "div",
+                        class: "mdc-layout-grid",
+                        $components: [
+                            {
+                                $type: "div",
+                                class: "mdc-layout-grid__inner",
+                                $components: [
+                                    {
+                                        $type: "div",
+                                        class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                        $components: [
+                                            {
+                                                $type: "h1",
+                                                class: "mdc-typography--headline4",
+                                                $text: 'Worlds for ' + userAlias
+                                            }
+                                        ]
+                                    },
+                                    {
+                                        $type: "div",
+                                        class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                        $components: [].concat(worldsGUI)
+                                    },
+                                ]
+                            }
+                        ]
+                    }
+                ]
+    }
 
     initUser() {
 
@@ -107,8 +261,8 @@ class IndexApp {
                 userEl._status = 'Welcome ' + alias + '!';
                 //userEl.style.backgroundColor = '#e6e6e6';   
                 userEl.$update();
-                document.querySelector('#worldGUI').$update();
-                document.querySelector('#main').$update();
+                // document.querySelector('#worldGUI').$update();
+                // document.querySelector('#main').$update();
 
                 _LCSUSER.get('profile').once(function (data) { console.log(data) })
 
@@ -122,6 +276,10 @@ class IndexApp {
                     _LCSUSER.get('profile').put(profile);
                 })
 
+                let actionsGUI = document.querySelector('#worldActionsGUI');
+                if (actionsGUI)
+                    actionsGUI._refresh();
+
                 new Noty({
                     text: alias + ' is succesfully authenticated!',
                     timeout: 2000,
@@ -154,7 +312,9 @@ class IndexApp {
                             "label": 'Default World Protos',
                             "onclick": function (e) {
                                 e.preventDefault();
-                                _app.indexApp.getAppDetailsFromDefaultDB('protos');
+                                page("/app/worlds/protos")
+                                //window.location.pathname = "/app/worlds/protos"
+                                //_app.indexApp.getAppDetailsFromDefaultDB('protos');
 
                             }
                         }),
@@ -163,7 +323,9 @@ class IndexApp {
                             "label": 'Default World States',
                             "onclick": function (e) {
                                 e.preventDefault();
-                                _app.indexApp.getAppDetailsFromDefaultDB('states');
+                                page("/app/worlds/states")
+                               // window.location.pathname = "/app/worlds/states"
+                                //_app.indexApp.getAppDetailsFromDefaultDB('states');
 
                             }
                         })
@@ -220,6 +382,7 @@ class IndexApp {
                                 "label": 'PROFILE',
                                 "onclick": function (e) {
                                     e.preventDefault();
+                                    //page("/profile")
                                     window.location.pathname = "/profile"
                                 }
                             }),
@@ -232,7 +395,8 @@ class IndexApp {
                                 "onclick": function (e) {
                                     e.preventDefault();
                                     let alias = _LCSUSER.is.alias;
-                                    page.redirect('/' + alias + '/worlds/protos');
+                                    //window.location.pathname = '/' + alias + '/worlds/protos'
+                                    page('/' + alias + '/worlds/protos');
                                     //_app.indexApp.getWorldsProtosFromUserDB(alias);
                                 }
                             }),
@@ -242,7 +406,9 @@ class IndexApp {
                                 "onclick": function (e) {
                                     e.preventDefault();
                                     let alias = _LCSUSER.is.alias;
-                                    page.redirect('/' + alias + '/worlds/states');
+                                   // window.location.pathname = '/' + alias + '/worlds/states'
+                                   page('/' + alias + '/worlds/states');
+                                   // page.redirect('/' + alias + '/worlds/states');
                                     //_app.indexApp.getWorldsFromUserDB(alias);       
                                 }
                             })
@@ -265,7 +431,6 @@ class IndexApp {
                 ].concat(gui)
             }
         }
-
     
         let loginGUI =
         {
@@ -415,258 +580,237 @@ class IndexApp {
 
         }
 
-        document.querySelector("#userLobby").$cell({
-            id: "userLobby",
-            $cell: true,
-            $type: "div",
-            $components: [ 
+        // document.querySelector("#userLobby").$cell({
+        //     id: "userLobby",
+        //     $cell: true,
+        //     $type: "div",
+        //     $components: [ 
                
-                userGUI, loginGUI, _app.widgets.divider, worldGUI]
-        }
-        );
-    }
+        //         userGUI, loginGUI, _app.widgets.divider, worldGUI]
+        // }
+        // );
 
-    parseAppInstancesData(data) {
+        return [userGUI, loginGUI, _app.widgets.divider, worldGUI]
 
-        if (data == "{}") {
-            var el = document.querySelector(".instance");
-            if (el) {
-                var topEl = el.parentNode;
-                topEl.removeChild(el);
-            }
-            // let removeElements = elms => Array.from(elms).forEach(el => el.remove()); 
-        }
-
-        let jsonObj = JSON.parse(data);
-        var parsed = {};
-        for (var prop in jsonObj) {
-            var name = prop.split('/')[1];
-            if (parsed[name]) {
-                parsed[name][prop] = jsonObj[prop];
-            } else {
-                parsed[name] = {};
-                parsed[name][prop] = jsonObj[prop];
-            }
+    }
 
-        }
-        //console.log(parsed);
+    refresh() {
+        // socket.emit('getWebAppUpdate', "");
+    }
 
-        if (Object.entries(this.worlds).length !== 0)
-            document.querySelector("#main")._emptyLists();
 
-        for (var prop in parsed) {
-            var name = prop;
-            let obj = Object.entries(parsed[prop]);
-            var lists = {};
-            obj.forEach(el => {
-                let sotSave = prop;
+    parseOnlineData(data) {
 
-                if (el[1].loadInfo['save_name']) {
-                    let saveName = prop + '/load/' + el[1].loadInfo.save_name;
-                    if (!lists[saveName])
-                        lists[saveName] = {};
+        let parcedData = _app.parseAppInstancesData(data);
 
-                    lists[saveName][el[0]] = el[1]
-                } else {
-                    if (!lists[name])
-                        lists[name] = {};
+        //if (Object.entries(parcedData).length !== 0)
+        let onlineGUIs = document.querySelectorAll('.online');
 
-                    lists[name][el[0]] = el[1]
+        onlineGUIs.forEach(function (item) {
+            item._refresh(parcedData)
+        });
 
-                }
-            });
+    }
 
-            // console.log(lists);
+    createWorldCard(id, type) {
+        let self = this;
 
-            Object.entries(lists).forEach(list => {
+        let onlineGUI = {
+            $cell: true,
+            id: "onlineGUI_" + id,
+            class: "online",
+            $type: "div",
+            _instances: {},
+            _worldListItem: function (m) {
+                return {
+                    $type: "li",
+                    class: "mdc-list-item",
+                    $components: [
+                        {
+                            $type: "span",
+                            class: "world-link mdc-list-item__text",
+                            $components: [
+                                {
+                                    $type: "span",
+                                    class: "mdc-list-item__primary-text",
+                                    $components: [
+                                        {
+                                            $type: "a",
+                                            $text: m[0],
+                                            target: "_blank",
+                                            href: window.location.protocol + "//" + window.location.host + "/" + m[1].user + m[0],
+                                            onclick: function (e) {
+                                                //self.refresh();
+                                            }
+                                        },
+                                    ]
+                                },
+                                {
+                                    $type: "span",
+                                    class: "mdc-list-item__secondary-text",
+                                    $text: self.language.t('users') + m[1].clients
+                                }
+                            ]
+                        }
+                    ]
+                }
+            },
+            $components: [],
+            _refresh: function (data) {
+                if (data) {
+                    if (Object.entries(data).length !== 0) {
+                        if (this._worldInfo) {
+                            let insts = Object.entries(data).filter(el => el[0] == this._worldInfo.worldName);
+                            if (insts.length !== 0)
+                                this._instances = insts[0][1];
+                        }
+                    } else {
+                        this._instances = {}
+                    }
 
-                let element = document.getElementById(list[0] + 'List');
-                if (element) {
-                    element._setListData(list[1]);
                 }
-            })
-        }
-        // console.log(data)
-    }
 
+            },
+            $init: function () {
+                this._refresh();
+            },
+            $update: function () {
+                if (this._instances) {
+                    let cardListData = Object.entries(this._instances).filter(el => el[1].user == this._worldInfo.userAlias);
+                    this.$components = [
+                        {
+                            $type: "hr",
+                            class: "mdc-list-divider"
+                        }
+                    ].concat(cardListData.map(this._worldListItem))
+                }
 
-    initWorldsListGUI() {
+            }
+        }
 
-        var self = this;
-        let worldsListGUI = {
 
+        return {
             $cell: true,
+            id: 'worldCard_' + id,
             $type: "div",
-            id: "main",
-            _status: "",
-            _jsonData: {},
-            _emptyLists: function () {
-                Object.entries(this._jsonData).forEach(function (element) {
-                    //console.log(element);
-                    let el = document.getElementById(element[0] + 'List');
-                    if (el)
-                        el._setListData({});
-                });
+            _worldInfo: {},
+            _refresh: function (data) {
+                this._worldInfo = data
             },
+            // _getWorldInfo: async function () {
+            //     //get space for user
+            //     let info = await _app.getWorldInfo(user, space);
+            //     this._refresh(info);
+            // },
+            // _getStateInfo: async function () {
+            //     //get space for user
+            //     let info = await _app.getStateInfo(user, space, saveName);
+            //     this._refresh(info);
+            // },
             $init: function () {
-                this._jsonData = {} //data//JSON.parse(data);
+                //get space for user
+                // if (!saveName) {
+                //     this._getWorldInfo();
+                // } else {
+                //     this._getStateInfo();
+                // }
             },
-            _makeWorldCard: function (m) {
-                let langID = localStorage.getItem('krestianstvo_locale');
-                var appInfo = m
-                if (langID) {
-                    if (m[1][langID]) {
-                        appInfo = [m[0], m[1][langID], m[1].user, m[1].type, m[1].created, m[1].modified]
+            $update: function () {
+                //console.log(this._worldInfo);
+                this.$components = [this._updateCard()]
+            },
+            $components: [],
+            _updateCard: function () {
+
+                let desc = this._worldInfo;
+
+
+                if (!desc || Object.keys(desc).length == 0) {
+                    return {
+                        $type: "h1",
+                        class: "mdc-typography--headline4",
+                        $text: "ERROR: NO WORLD!"
                     }
                 }
-                return {
-                    $cell: true,
-                    $type: "div",
-                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
-                    $components: [
-                        this._worldCardDef(appInfo)
-                    ]
-                }
 
-            },
-            $update: function () {
-                this.$components = [
-                    {
-                        $type: "div",
-                        class: "mdc-layout-grid",
-                        $components: [
-                            {
-                                $type: "div",
-                                class: "mdc-layout-grid__inner",
-                                $components: [
-                                    {
-                                        $type: "div",
-                                        class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
-                                        $components: [
-                                            {
-                                                $type: "H3",
-                                                $text: this._status
-                                            }
-                                        ]
-                                    }
-                                ]
-                            },
-                            {
-                                $type: "div",
-                                class: "mdc-layout-grid__inner",
-                                $components: Object.entries(this._jsonData).sort(function(el1, el2){
-                                    return parseInt(el2[1].created) - parseInt(el1[1].created)
-                                }).map(this._makeWorldCard)
-                            }
-                        ]
 
-                    },
+                let userGUI = [];
+                let online = [];
 
+                let cardInfo = {
+                    "title": ""
+                };
 
-                ]
-            },
-            _worldCardDef: function (desc) {
 
-                var userGUI = [];
+                if (type == "full") {
 
-                if (desc[3] == 'proto') {
-                    userGUI.push(
-
-                        {
-                            $type: "a",
-                            class: "mdc-button mdc-button--compact mdc-card__action",
-                            $text: "States",
-                            onclick: function (e) {
-                                //console.log('clone');
-                                self.showOnlySaveStates(desc[0], desc[2]);
-                                //self.refresh();
-                            }
+                } else {
+                    userGUI.push({
+                        $type: "a",
+                        class: "mdc-button mdc-button--compact mdc-card__action mdc-button--outlined",
+                        $text: "Details",
+                        onclick: function (e) {
+                            e.preventDefault();
+                            window.location.pathname = "/" + desc.userAlias + '/' + desc.worldName + '/about'
                         }
-                    )
+                    });
                 }
 
-                if (_LCSUSER.is) {
+                userGUI.push({
+                    $type: "a",
+                    class: "mdc-button mdc-button--raised mdc-card__action ",
+                    $text: self.language.t('start'),//"Start new",
+                    target: "_blank",
+                    href: "/" + desc.userAlias + '/' + desc.worldName,
+                    onclick: function (e) {
+                        //self.refresh();
+                    }
+                });
 
-                    if (_LCSUSER.is.alias == desc[2]) {
-                        userGUI.push(
-                            {
-                                $type: "a",
-                                class: "mdc-button mdc-button--compact mdc-card__action",
-                                $text: "Edit info",
-                                //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
-                                onclick: function (e) {
-                                    //'/:user/:type/:name/edit/:file'
-                                    if (desc[3] == 'proto') {
-                                        window.location.pathname = "/" + desc[2] + '/proto/' + desc[0] + '/edit/info_json'
-                                    } else if (desc[3] == 'saveState') {
-                                        let names = desc[0].split('/');
-                                        let filename = ('savestate_/' + names[0] + '/' + names[2] + '_info_vwf_json').split('/').join("~");
-                                        window.location.pathname = "/" + desc[2] + '/state/' + names[0] + '/edit/' + filename;
-                                    }
-                                    //self.refresh();
-                                }
-                            }
-                        );
 
-                        if (desc[3] == 'proto') {
-                            userGUI.push(
-                                {
-                                    $type: "a",
-                                    class: "mdc-button mdc-button--compact mdc-card__action",
-                                    $text: "Edit proto",
-                                    //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
-                                    onclick: function (e) {
-                                        window.location.pathname = "/" + desc[2] + '/proto/' + desc[0] + '/edit/index_vwf_yaml'
-                                    }
-                                }
-                            );
-                        }
-                    }
+                if (desc.type == 'saveState') {
+                    cardInfo.title = desc.worldName.split('/')[2];
+                }
 
-                    if (desc[3] == 'proto') {
-                        userGUI.push(
-                            {
-                                $type: "a",
-                                class: "mdc-button mdc-button--compact mdc-card__action",
-                                $text: self.language.t('clone proto'),//"clone",
-                                onclick: function (e) {
-                                    //console.log('clone');
-                                    _app.cloneWorldPrototype(desc[0], desc[2]);
-                                    //self.refresh();
-                                }
-                            }
+                if (desc.type == 'proto') {
+                    cardInfo.title = desc.worldName;
 
-                        )
-                    } else if (desc[3] == 'saveState') {
-                        // userGUI.push(
-                        //     {
-                        //         $type: "a",
-                        //         class: "mdc-button mdc-button--compact mdc-card__action mdc-button--outlined",
-                        //         $text: "Clone",
-                        //         onclick: function (e) {
-                        //             //console.log('clone');
-
-                        //             //self.cloneWorldState(desc[0], desc[2]);
-
-                        //             //self.refresh();
-                        //         }
-                        //     })
-                    }
+                    // userGUI.push(
+
+                    //     {
+                    //         $type: "a",
+                    //         class: "mdc-button mdc-button--compact mdc-card__action",
+                    //         $text: "States",
+                    //         onclick: async function (e) {
 
+                    //             e.preventDefault();
+                    //             window.location.pathname = "/" + desc.userAlias + '/' + desc.worldName +'/about'
+                    //             //console.log('clone');
+
+                    //             // document.querySelector('#worldStatesGUI')._refresh({});
+                    //             // let data = await _app.getSaveStates(desc.userAlias, desc.worldName);
+                    //             // document.querySelector('#worldStatesGUI')._refresh(data);
+ 
+                    //         }
+                    //     }
+                    // )
                 }
 
+
+
+                online.push(onlineGUI);
+
                 return {
                     $cell: true,
                     $type: "div",
                     class: "mdc-card world-card",
-                    _appInfo: desc,
                     $components: [
                         {
                             $type: "section",
                             class: "mdc-card__media world-card__16-9-media",
                             $init: function () {
-                                if (desc[1].imgUrl !== "") {
-                                    this.style.backgroundImage = 'linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3) ), url(' + desc[1].imgUrl + ')';
+                                if (desc.info.imgUrl !== "") {
+                                    this.style.backgroundImage = 'linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3) ), url(' + desc.info.imgUrl + ')';
                                 }
                             }
                         },
@@ -677,20 +821,35 @@ class IndexApp {
                                 {
                                     $type: "h1",
                                     class: "mdc-card__title mdc-card__title--large",
-                                    $text: desc[1].title
+                                    $text: desc.info.title
                                 },
                                 {
                                     $type: "h2",
                                     class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
-                                    $text: desc[1].text 
+                                    $text: desc.info.text
+                                },
+                                {
+                                    $type: "span",
+                                    class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
+                                    $text: 'id: '
+                                },
+                                {
+                                    $type: "input",
+                                    type: "text",
+                                    disabled: "",
+                                    style: "font-size:18px",
+                                    value: cardInfo.title
+                                },
+                                {
+                                    $type: "p",
                                 },
                                 {
                                     $type: "span",
                                     class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
-                                    $text: 'created: ' + (new Date(desc[4])).toUTCString()
+                                    $text: 'created: ' + (new Date(desc.created)).toUTCString()
                                 },
                                 {
-                                    $type: "p", 
+                                    $type: "p",
                                 }
                                 // ,{
                                 //     $type: "span",
@@ -703,16 +862,7 @@ class IndexApp {
                             $type: "section",
                             class: "mdc-card__actions",
                             $components: [
-                                {
-                                    $type: "a",
-                                    class: "mdc-button mdc-button--compact mdc-card__action mdc-button--outlined",
-                                    $text: self.language.t('start'),//"Start new",
-                                    target: "_blank",
-                                    href: "/" + desc[2] + '/' + desc[0],
-                                    onclick: function (e) {
-                                        self.refresh();
-                                    }
-                                }
+
                             ].concat(userGUI)
                         },
 
@@ -720,275 +870,95 @@ class IndexApp {
                             $type: "section",
                             class: "mdc-card__actions",
                             $components: [
-
                                 {
-                                    $type: "ul",
-                                    _listData: {},
-                                    _setListData: function (data) {
-                                        this._listData = data;
-                                    },
-                                    class: "mdc-list mdc-list--two-line",
-                                    'aria-orientation': "vertical",
-                                    id: desc[0] + 'List',
-                                    $update: function () {
-                                        var connectText = {}
-
-                                        let cardListData = Object.entries(this._listData).filter(el => el[1].user == this._appInfo[2]);
-
-                                        if (cardListData.length !== 0) {
-                                            connectText = {
-                                                // $type: "span",
-                                                // class: "mdc-theme--text-secondary",
-                                                // $text: "...or connect to:"
-                                            }
-                                        }
-                                        this.$components = [
-                                            {
-                                                $type: "hr",
-                                                class: "mdc-list-divider"
-                                            }
-                                        ].concat(cardListData.map(this._worldListItem))
-                                        //     [connectText]
-                                        // }].concat(Object.entries(this._listData).map(this._worldListItem))
-
-
-                                    },
-                                    _worldListItem: function (m) {
-                                        return {
-                                            $type: "li",
-                                            class: "mdc-list-item",
-                                            $components: [
-                                                {
-                                                    $type: "span",
-                                                    class: "world-link mdc-list-item__text",
-                                                    $components: [
-                                                        {
-                                                            $type: "span",
-                                                            class: "mdc-list-item__primary-text",
-                                                            $components: [
-                                                                {
-                                                                    $type: "a",
-                                                                    $text: m[0],
-                                                                    target: "_blank",
-                                                                    href: window.location.protocol + "//" + window.location.host + "/" + m[1].user + m[0],
-                                                                    onclick: function (e) {
-                                                                        //self.refresh();
-                                                                    }
-                                                                },
-                                                            ]
-                                                        },
-                                                        {
-                                                            $type: "span",
-                                                            class: "mdc-list-item__secondary-text",
-                                                            $text: self.language.t('users') + m[1].clients
-                                                        }
-                                                    ]
-                                                }
-                                            ]
-                                        }
-                                    }
+                                    $type: 'div',
+                                    $text: 'online now: '
                                 }
-                            ]
+                            ].concat(online)
                         }
                     ]
                 }
             }
         }
 
-        document.querySelector("#main").$cell({
-            $cell: true,
-            $type: "div",
-            $components: [worldsListGUI]
-        })
-
     }
 
-    async showOnlySaveStates(index, userAlias) {
-
-        let userPub = await _LCSDB.get('users').get(userAlias).get('pub').once().then();
-
-        var db = _LCSDB.user(userPub);
-
-        if (_LCSUSER.is) {
-            if (_LCSUSER.is.alias == userAlias)
-                db = _LCSUSER;
-        }
-
-        this.worlds = {};
-        document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
-        document.querySelector("#main")._status = "Save states of the World: " + index + ' for user: ' + userAlias;
-        document.querySelector("#main").$update();
-        //let userAlias = _LCSUSER.is.alias;
-
-        db.get('documents').get(index).once(save => {
-            if (save) {
-                let saves = Object.keys(save).filter(el => el.includes('_info_vwf_json'));
-                console.log(saves);
-
-                if (saves) {
-                    saves.forEach(el => {
-                        db.get('documents').get(index).get(el).once(res => {
-                            if (res) {
-                                let created = res.created ? res.created: res.modified;
-
-                                let fileName = el.split('/')[2].replace('_info_vwf_json', "");
-                                let world = JSON.parse(res.file);
-                                let root = Object.keys(world)[0];
-                                world[root].user = userAlias;
-                                world[root].type = 'saveState';
-                                world[root].created = created;
-                                world[root].modified = res.modified;
-                                this.worlds[index + '/load/' + fileName] = world[root];
-                                document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
-                            }
-                        })
-                    })
-                }
-            }
-        })
-    }
-
-    async getWorldsProtosFromUserDB(userAlias) {
-
-        let userPub = await _LCSDB.get('users').get(userAlias).get('pub').once().then();
-
-        console.log('get user worlds for: ' + userAlias);
-        this.worlds = {};
-        document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
-
-        if (!userPub) {
-            document.querySelector("#main")._status = "no such user";
-            document.querySelector("#main").$update();
-        }
-
-        if (userPub) {
-
-            document.querySelector("#main")._status = "Worlds protos for: " + userAlias;
-            document.querySelector("#main").$update();
-
-            var db = _LCSDB.user(userPub);
-
-            if (_LCSUSER.is) {
-                if (_LCSUSER.is.alias == userAlias)
-                    db = _LCSUSER;
-            }
-
-            db.get('worlds').map().once((w, index) => {
-
-                if (w) {
-                    db.get('worlds').get(index).get('info_json').once(res => {
-
-                        if (res) {
-
-                            let created = res.created ? res.created: res.modified;
-
-                            let world = JSON.parse(res.file);
-                            let root = Object.keys(world)[0];
-                            world[root].user = userAlias;
-                            world[root].type = 'proto';
-                            world[root].created = created;
-                            world[root].modified = res.modified;
-                            this.worlds[index] = world[root];
-                            document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
-                        }
-
-                    })
+    createWorldsGUI(userAlias, worldName) {
+        let self = this;
+        let id = worldName?worldName + '_' + userAlias: "allWorlds_" + userAlias
+        let headerText = worldName?'States for ' + worldName: 'All Worlds Protos'
 
+        let worldCards = {
+            $cell: true,
+            id: id,
+            $type: "div",
+            $components: [],
+            _states: {},
+            _refresh: function (data) {
+                this._states = data
+            },
+            $init: async function () {
+                //this._refresh();
+            },
+            _makeWorldCard: function (data) {
+                let cardID = data[1].userAlias + '_' + data[1].worldName + '_' + data[0];
+                let card = self.createWorldCard(cardID, 'min');
+                card._worldInfo = data[1];
+                card.$update();
+                return {
+                    $cell: true,
+                    $type: "div",
+                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
+                    $components: [
+                        card
+                        //self.createWorldCard(data[1].userAlias, data[1].worldName, data[0])
+                        //this._worldCardDef(appInfo)
+                    ]
                 }
-            })
-        }
-
-    }
-
-
-    async getWorldsFromUserDB(userAlias) {
-
-        let userPub = await _LCSDB.get('users').get(userAlias).get('pub').once().then();
-
-        console.log('get user worlds for: ' + userAlias);
-        this.worlds = {};
-        document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
-
-        if (!userPub) {
-            document.querySelector("#main")._status = "no such user";
-            document.querySelector("#main").$update();
-        }
-
-        if (userPub) {
-
-            document.querySelector("#main")._status = "Worlds states for: " + userAlias;
-            document.querySelector("#main").$update();
-
-            var db = _LCSDB.user(userPub);
-
-            if (_LCSUSER.is) {
-                if (_LCSUSER.is.alias == userAlias)
-                    db = _LCSUSER;
-            }
-
-
-            db.get('worlds').map().once((w, index) => {
-
-                if (w) {
-
-                    db.get('documents').get(index).once(save => {
-                        if (save) {
-                            let saves = Object.keys(save).filter(el => el.includes('_info_vwf_json'));
-                            console.log(saves);
-
-                            if (saves) {
-
-                                saves.forEach(el => {
-
-                                    db.get('documents').get(index).get(el).once(res => {
-
-                                        if (res) {
-
-                                            let created = res.created ? res.created: res.modified;
-
-                                            let fileName = el.split('/')[2].replace('_info_vwf_json', "");
-                                            let world = JSON.parse(res.file);
-                                            let root = Object.keys(world)[0];
-                                            world[root].user = userAlias;
-                                            world[root].type = 'saveState';
-                                            world[root].created = created;
-                                            world[root].modified = res.modified;
-                                            this.worlds[index + '/load/' + fileName] = world[root];
-                                            document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
-
-                                        }
-                                    })
+                //console.log(data);
+            },
+            $update: function () {
+                this.$components = [
+                    {
+                        $type: "div",
+                        class: "mdc-layout-grid",
+                        $components: [
+                            {
+                                $type: "div",
+                                class: "mdc-layout-grid__inner",
+                                $components: [
+                                    {
+                                        $type: "div",
+                                        class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                        $components: [
+                                            {
+                                                $type: "H3",
+                                                $text: headerText
+                                            }
+                                        ]
+                                    }
+                                ]
+                            },
+                            {
+                                $type: "div",
+                                class: "mdc-layout-grid__inner",
+                                $components: Object.entries(this._states)
+                                .filter(el =>Object.keys(el[1]).length !== 0)
+                                .sort(function (el1, el2) {
+                                    return parseInt(el2[1].created) - parseInt(el1[1].created)
                                 })
+                                .map(this._makeWorldCard)
                             }
-                        }
-                    })
-                }
-            })
-        }
-    }
-
-
-    async getAppDetailsFromDefaultDB(type) {
-
-        let defaultUserPUB = await _LCSDB.get('lcs/app').path('pub').once().then();
-        var userAlias = await _LCSDB.user(defaultUserPUB).get('alias').once().then();
-
-        page.redirect('/' + userAlias + '/worlds/' + type);
-
-    }
-
-    async getAppDetailsFromDB() {
+                        ]
 
-        let defaultUserPUB = await _LCSDB.get('lcs/app').path('pub').once().then();
-        var userAlias = await _LCSDB.user(defaultUserPUB).get('alias').once().then();
+                    }
 
-        if (userAlias)
-            this.getWorldsProtosFromUserDB(userAlias);
-    }
 
+                ]
+            }
+        }
 
-    refresh() {
-        // socket.emit('getWebAppUpdate', "");
+        return worldCards
     }
 
 

+ 1 - 1
public/web/locale/en/index.html

@@ -3,7 +3,7 @@
     alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_green_007200.png">
 </a>
 
-<div id="header" class="mdc-layout-grid mdc-layout-grid--align-left">
+<div id="headerGUI" class="mdc-layout-grid mdc-layout-grid--align-left">
   <div class="mdc-layout-grid__inner">
     <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
       <span class="mdc-typography--subtitle1 mdc-theme--text-secondary-on-background mdc-typography">

+ 336 - 0
public/web/world-app.js

@@ -0,0 +1,336 @@
+import page from '/lib/page.mjs';
+
+class WorldApp {
+    constructor(userAlias, worldName, saveName) {
+        console.log("app constructor");
+
+        this.userAlias = userAlias;
+        this.worldName = worldName;
+        this.saveName = saveName;
+
+        //this.worlds = {};
+        this.language = _LangManager.language;
+          
+    }
+
+
+    createWorldStatesGUI() {
+        let self = this;
+
+        let worldStatesGUI = {
+            $cell: true,
+            id: "worldStatesGUI",
+            $type: "div",
+            $components: [],
+            _states: {},
+            _refresh: function (data) {
+                this._states = data
+            },
+            $init: async function () {
+                //this._refresh();
+            },
+            _makeWorldCard: function (data) {
+                let cardID = data[1].userAlias + '_' + data[1].worldName + '_' + data[0];
+                let card = _app.indexApp.createWorldCard(cardID, 'min');
+                card._worldInfo = data[1];
+                card.$update();
+                return {
+                    $cell: true,
+                    $type: "div",
+                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
+                    $components: [
+                        card
+                        //self.createWorldCard(data[1].userAlias, data[1].worldName, data[0])
+                        //this._worldCardDef(appInfo)
+                    ]
+                }
+                //console.log(data);
+            },
+            $update: function () {
+                this.$components = [
+                    {
+                        $type: "div",
+                        class: "mdc-layout-grid",
+                        $components: [
+                            {
+                                $type: "div",
+                                class: "mdc-layout-grid__inner",
+                                $components: [
+                                    {
+                                        $type: "div",
+                                        class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                        $components: [
+                                            {
+                                                $type: "H3",
+                                                $text: 'States'
+                                            }
+                                        ]
+                                    }
+                                ]
+                            },
+                            {
+                                $type: "div",
+                                class: "mdc-layout-grid__inner",
+                                $components: Object.entries(this._states)
+                                .filter(el =>Object.keys(el[1]).length !== 0)
+                                .sort(function (el1, el2) {
+                                    return parseInt(el2[1].created) - parseInt(el1[1].created)
+                                })
+                                .map(this._makeWorldCard)
+                            }
+                        ]
+
+                    }
+
+
+                ]
+            }
+        }
+
+        return worldStatesGUI
+    }
+
+    async initWorldGUI() {
+
+    //  _LCSDB.on('auth',
+    //     function (ack) {
+
+    //             document.querySelector('#worldActionsGUI')._refresh();
+            
+    //     })
+
+        let self = this;
+        let user = this.userAlias;
+        let space = this.worldName;
+        let saveName = this.saveName;
+
+        let el = document.createElement("div");
+        el.setAttribute("id", "aboutWorld");
+        document.body.appendChild(el);
+
+        let cardID = user + '_' + space + '_' + (saveName ? saveName : "");
+        let worldCardGUI = _app.indexApp.createWorldCard(cardID, 'full');
+        let worldStatesGUI = [];
+
+        var info = {};
+
+        if (!saveName) {
+            info = await _app.getWorldInfo(user, space);
+        } else {
+            info = await _app.getStateInfo(user, space, saveName);
+        }
+        worldCardGUI._worldInfo = info;
+        worldCardGUI.$update();
+
+        if (!saveName) {
+            let statesData = await _app.getSaveStates(user, space);
+            let worldStates = this.createWorldStatesGUI();
+            worldStates._states = statesData;
+            worldStates.$update();
+            worldStatesGUI.push(worldStates);
+        }
+
+
+        let actionsGUI = {
+            $cell: true,
+            id: "worldActionsGUI",
+            $type: "div",
+            $components: [],
+            _worldInfo: {},
+            _refresh: function () {
+
+                this._worldInfo = {
+                    'userAlias': self.userAlias,
+                    'worldName': self.saveName ? self.worldName + '/load/' + self.saveName : self.worldName,
+                    'type': self.saveName ? 'saveState' : 'proto'
+                }
+
+                //    let worldCard = document.querySelector('#worldCard');
+                //    if(worldCard){
+                //        this._worldInfo = worldCard._worldInfo;
+                //    } 
+            },
+            $init: function () {
+                if (_LCSUSER.is) {
+                    this._refresh();
+                }
+            },
+            $update: function () {
+
+                let desc = this._worldInfo;
+                let userGUI = [];
+
+                // if(!desc){
+                //     this.$components = [];
+                //     return
+                // }
+
+                if (_LCSUSER.is) {
+                    if (_LCSUSER.is.alias == desc.userAlias) {
+                        userGUI.push(
+                            {
+                                $type: "a",
+                                class: "mdc-button mdc-button--raised mdc-card__action actionButton",
+                                $text: "Edit info",
+                                //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
+                                onclick: function (e) {
+                                    //'/:user/:type/:name/edit/:file'
+                                    if (desc.type == 'proto') {
+                                        window.location.pathname = "/" + desc.userAlias + '/proto/' + desc.worldName + '/edit/info_json'
+                                    } else if (desc.type == 'saveState') {
+                                        let names = desc.worldName.split('/');
+                                        let filename = ('savestate_/' + names[0] + '/' + names[2] + '_info_vwf_json').split('/').join("~");
+                                        window.location.pathname = "/" + desc.userAlias + '/state/' + names[0] + '/edit/' + filename;
+                                    }
+                                    //self.refresh();
+                                }
+                            }
+                        );
+
+                        if (desc.type == 'proto') {
+                            userGUI.push(
+                                {
+                                    $type: "a",
+                                    class: "mdc-button mdc-button--raised mdc-card__action actionButton",
+                                    $text: "Edit proto",
+                                    //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
+                                    onclick: function (e) {
+                                        window.location.pathname = "/" + desc.userAlias + '/proto/' + desc.worldName + '/edit/index_vwf_yaml'
+                                    }
+                                }
+                            );
+
+                            userGUI.push(
+                                {
+                                    $type: "a",
+                                    class: "mdc-button mdc-button--raised mdc-card__action actionButton",
+                                    $text: "Delete",
+                                    //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
+                                    onclick: function (e) {
+                                        _app.deleteWorld(desc.worldName, 'proto');
+                                    }
+                                }
+                            );
+                        }
+
+
+                        if (desc.type == 'saveState') {
+                            userGUI.push(
+                                {
+                                    $type: "a",
+                                    class: "mdc-button mdc-button--raised mdc-card__action actionButton",
+                                    $text: "Delete",
+                                    //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
+                                    onclick: function (e) {
+                                        _app.deleteWorld(desc.worldName, 'state');
+                                    }
+                                }
+                            );
+                        }
+
+
+                    }
+
+                    if (desc.type == 'proto') {
+                        userGUI.push(
+                            {
+                                $type: "a",
+                                class: "mdc-button mdc-button--raised mdc-card__action actionButton",
+                                $text: self.language.t('clone proto'),//"clone",
+                                onclick: function (e) {
+                                    //console.log('clone');
+                                    _app.cloneWorldPrototype(desc.worldName, desc.userAlias);
+                                    //self.refresh();
+                                }
+                            }
+
+                        )
+                    } else if (desc.type == 'saveState') {
+
+
+                        // userGUI.push(
+                        //     {
+                        //         $type: "a",
+                        //         class: "mdc-button mdc-button--compact mdc-card__action mdc-button--outlined",
+                        //         $text: "Clone",
+                        //         onclick: function (e) {
+                        //             //console.log('clone');
+
+                        //             //self.cloneWorldState(desc[0], desc[2]);
+
+                        //             //self.refresh();
+                        //         }
+                        //     })
+                    }
+
+                }
+
+
+
+                this.$components = [
+                    {
+                        $type: "div",
+                        $text: "World actions:"
+                    }
+                ].concat(userGUI)
+            }
+        }
+
+    
+        document.querySelector("#aboutWorld").$cell({
+            id: 'aboutWorld',
+            $cell: true,
+            $type: "div",
+            $components: [
+                {
+                    $type: "div",
+                    class: "mdc-layout-grid",
+                    $components: [
+                        {
+                            $type: "div",
+                            class: "mdc-layout-grid__inner",
+                            $components: [
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                        {
+                                            $type: "h1",
+                                            class: "mdc-typography--headline4",
+                                            $text: self.worldName + ' by ' + self.userAlias
+                                        }
+                                    ]
+                                },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
+                                    $components: [
+                                        worldCardGUI
+                                    ]
+                                },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                        actionsGUI
+                                    ]
+                                },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                    ].concat(worldStatesGUI)
+                                },
+                            ]
+                        }
+                    ]
+                }
+            ]
+        })
+
+
+    }
+
+}
+
+export { WorldApp }

Some files were not shown because too many files changed in this diff