Nikolay Suslov 2 лет назад
Родитель
Сommit
cce01bf879
3 измененных файлов с 362 добавлено и 6 удалено
  1. 76 5
      public/core/app.js
  2. 1 1
      public/web/footer.js
  3. 285 0
      public/web/standalone.js

+ 76 - 5
public/core/app.js

@@ -1,6 +1,6 @@
 /*
 The MIT License (MIT)
-Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+Copyright (c) 2014-2021 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
 
 Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
 */
@@ -12,12 +12,19 @@ import { WorldApp } from '/web/world-app.js';
 import { Widgets } from '/lib/ui/widgets.js';
 import {Spinner} from '/lib/ui/spinjs/spin.js';
 
+import { Standalone } from '/web/standalone.js';
+import '/lib/ui/shoelace/dist/shoelace.js';
+import { setBasePath } from '/lib/ui/shoelace/dist/utilities/base-path.js';
+import { h, text, patch } from '/lib/ui/superfine.js';
+
 import { createAdapter } from '/lib/fun/@most/adapter/dist/index.mjs';
 import *  as mostSubject from '/lib/fun/@most/subject/dist/index.all.js';
 
 class App {
   constructor() {
     console.log("app constructor");
+    setBasePath('/lib/ui/shoelace');
+
     this.widgets = new Widgets;
     //globals
     window._app = this;
@@ -75,6 +82,7 @@ class App {
     page('/debug', this.HandleDebugIndex);
     page('/settings', this.HandleSettingsIndex);
     page('/profile', this.HandleUserIndex);
+    page('/login', this.HandleUserLogin);
     page('/worlds', this.HandleIndex);
     page('/:user', this.HandleUserWorlds);
     page('/:user/worlds', this.HandleUserWorlds);
@@ -111,7 +119,11 @@ class App {
       'd3DoF': false,
       'd6DoF': false,
       'streamMsg': false,
-      'multisocket': false 
+      'multisocket': false,
+      'edit': false,
+      'standalone': true,
+      "standaloneWorldName": "concert0",
+      "standaloneUserName": "aaa"
     }
 
     let conf = JSON.parse(localStorage.getItem('lcs_config'));
@@ -186,6 +198,8 @@ class App {
     let manualConfig = localStorage.getItem('lcs_app_manual_settings');
     let lcsappConfig = localStorage.getItem('lcs_app');
 
+    let edit = localStorage.getItem('edit');
+
     localStorage.clear();
 
     if (config)
@@ -200,6 +214,9 @@ class App {
     if (lcsappConfig)
       localStorage.setItem('lcs_app', lcsappConfig);
 
+    if (edit)
+      localStorage.setItem('edit', edit);
+
   }
 
   setupLoadScreen(){
@@ -530,6 +547,32 @@ class App {
 
   }
 
+  HandleUserLogin(ctx) {
+
+    console.log("user login");
+
+    let userAlias = ctx.params.user;
+    let worldName = ctx.params.space;
+    let saveName = ctx.params.savename;
+
+    window._app.hideProgressBar();
+    //window._app.hideUIControl();
+
+
+    _app.generalIndex().then(res=>{
+
+    if (!_app.indexApp) {
+      _app.indexApp = new IndexApp('login');
+    }
+
+    // let worldApp = new WorldApp(userAlias, worldName, saveName);
+    // _app.helpers.getUserPub(userAlias).then(res => {
+    //   worldApp.makeGUI(res)
+    // })
+  })
+
+  }
+
   HandleWorldAbout(ctx) {
 
     console.log("about world");
@@ -1095,7 +1138,7 @@ class App {
     _app.generalIndex().then(r=>{
 
     if (!_app.indexApp) {
-      _app.indexApp = new IndexApp;
+      _app.indexApp = new IndexApp();
     }
     _app.indexApp.allWorldsForUser(user)
 
@@ -1265,6 +1308,8 @@ class App {
       '/lib/ui/ace/ace.js',
       '/lib/ui/drag-drop.js',
       '/lib/buffer5.6.0.min.js',
+      '/lib/ui/shoelace/dist/themes/base.css'
+
     ], {
       async: false,
       returnPromise: true
@@ -1297,14 +1342,31 @@ class App {
     console.log("INDEX");
 
     //window._app.hideUIControl();
+    if(_app.config.standalone){
+      loadjs([
+        '/lib/ui/shoelace/dist/themes/base.css'
+      ], {
+        async: false,
+        returnPromise: true
+      }).then(r=>{
+        _app.indexApp = new Standalone(_app.config.standaloneUserName, _app.config.standaloneWorldName);
+        _app.hideProgressBar();
+      })
+    } else {
+
     _app.generateFrontPage();
 
     _app.generalIndex().then(res=>{
       if (!_app.indexApp) {
-        _app.indexApp = new IndexApp('index');
-        _app.hideProgressBar();
+          _app.indexApp = new IndexApp('index');
+          _app.hideProgressBar();
       }
     })
+    }
+
+    
+
+
 
   }
 
@@ -1494,6 +1556,9 @@ class App {
       }
     } 
 
+    if(JSON.parse(localStorage.getItem('edit')) == true) 
+      vwfApp.conf.view['/drivers/view/editor'] = null;
+
       //check & set default proxy for world
       vwfApp.proxy = val.proxy ? val.proxy : _LCS_WORLD_USER.pub
 
@@ -1611,6 +1676,12 @@ class App {
       ],
       '/drivers/view/tone':[
         '/drivers/view/tonejs/Tone.js'
+      ],
+      '/drivers/view/pts':[
+        '/drivers/view/ptsjs/pts.js'
+      ],
+      '/drivers/view/two':[
+        '/drivers/view/twojs/two.js'
       ]
       
     }

+ 1 - 1
public/web/footer.js

@@ -60,7 +60,7 @@ class Footer {
 
                                             $type: "span",
                                             class: "mdc-typography  mdc-theme--text-hint-on-background",
-                                            $text: "| 2020 "
+                                            $text: "| 2021 "
                                         }
 
 

+ 285 - 0
public/web/standalone.js

@@ -0,0 +1,285 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2021 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+*/
+
+import { createSignal, onCleanup, createState, createEffect } from "/lib/ui/solid-js/dist/solid.js";
+import { render, For } from "/lib/ui/solid-js/web/dist/web.js";
+import h from "/lib/ui/solid-js/h/dist/h.js";
+import { styled, css, createGlobalStyles } from "/lib/ui/solid-js/solid-styled-components/src/index.js";
+
+
+class Standalone {
+    constructor(userName, worldName) {
+        console.log("standalone app constructor");
+        //setBasePath('/lib/ui/shoelace');
+
+        //this.entry = entry;
+        this.userName = userName;
+        this.worldName = worldName;
+
+        this.worlds = {};
+        this.instances = {};
+
+        if (!_app.isLuminary) {
+            //  this.initReflectorConnection();
+        }
+        this.initApp();
+    }
+
+    get containerClass() {
+        return css({
+            "align-items": "center",
+            display: "flex",
+            "justify-content": "center"
+        })
+    }
+
+    styledDiv() {
+        return styled("div")`
+      color: red;
+      font-size: 32px;
+      padding: 5px;
+      border: 2px solid black;
+      background-color: white;
+    `;
+    }
+
+    get GlobalStyles() {
+        return createGlobalStyles`
+            html,
+            body {
+                background: white;
+                margin: 10px;
+
+                min-height: 94vh;
+                display: flex;
+                flex-direction: column;
+            }
+`;
+    }
+
+    get CardInstance() {
+
+        const card = props => h("sl-card", {
+            class: "card-overview", style: {
+                "box-shadow": "var(--sl-shadow-medium)"
+            }
+        }, [
+            h("sl-qr-code", {
+                value: props.url, label: "Scan this code to connect!", size: "150", style: {
+                    display: "flex",
+                    "justify-content": "center",
+                    "align-items": "center"
+                }
+            }),
+            h("p"),
+            h("small", props.instanceID),
+            h("div", { slot: "footer" }, [
+
+                h("sl-button", {
+                    type: "primary", "pill": true, style: {
+                        "margin-right": "1rem"
+                    },
+                    href: props.url,
+                    target: "_blank"
+                    //onClick: () => { this.goToIndexWorld(props.url)} //href: props.url 
+                }, "Connect", [
+                    h("sl-badge", { pill: true, pulse: true, type: "warning" }, props.clients)
+                ]),
+                h("sl-tooltip", { content: "Settings" }, [
+                    h("sl-icon-button", { name: "gear" })]) //disabled: true
+            ])
+
+        ])
+
+        return card
+    }
+
+    get Card() {
+        let self = this;
+
+        const card = props => h("sl-card", {
+            class: "card-overview", style: {
+                "box-shadow": "var(--sl-shadow-x-large)",
+                "max-width": "400px"
+            }
+        }, [
+            h("img", {
+                slot: "image",
+                src: "/defaults/assets/concert/webimg.jpg",
+                alt: ""
+            }),
+            h("strong", "THIS IS NOT A CONCERT"),
+            h("br"),
+            h("small", "collaborative performance"),
+            h("div", { slot: "footer" }, [
+                h("sl-button", {
+                    type: "primary", "pill": {}, style: {
+                        "margin-right": "1rem"
+                    },
+                    href: props.url,
+                    target: "_blank"
+                    //onClick: () => { self.goToIndexWorld(props.url) },  
+                }, "Start new"),
+                h("div", [
+                    h("sl-tooltip", { content: "Info" }, [
+                        h("sl-icon-button", { name: "info-circle" })
+                    ]),
+                    h("sl-tooltip", { content: "Settings" }, [
+                        h("sl-icon-button", { name: "gear" })
+                    ])
+                ])
+
+            ])
+
+        ])
+
+        return card
+        //return (props) => h("h1", () => props.label, props.children);
+    }
+
+    goToIndexWorld(path) {
+        window.location.href = path;
+    }
+
+    get Label() {
+        return (props) => h("h1", () => props.label, props.children);
+    }
+
+    initApp() {
+        let self = this;
+        const App = () => {
+            const [state, setState] = createState({ instances: [] });
+            //const [count, setCount] = createSignal(0);
+
+            // createEffect(() => {
+            //     console.log("instances:", () => state.instances);
+            // });
+
+            self.initReflectorConnection(setState)
+
+            return [
+                h(self.GlobalStyles),
+                //self.containerClass
+                h('header', {}, [
+                    h('sl-details', {
+                        summary: "About", open: false, style: {
+                            "max-width": "800px"
+                        }
+                    }, [
+                        h("a", { class: "link-in-text", href: "https://browsersound.com/" }, "BROWSER 2021"),
+                        h("h4", ["A Festival of Web-based Music | June 11th-13th"]),
+
+                        h("strong", "Delia Ramos Rodríguez and Nikolay Suslov"),
+                        h("p", "Another perspective to what we normally (don't) see. The bowing after coming to the stage; the tuning of the instrument; the playing itself; the applause (?)."),
+                        h("p", "As always, but different."),
+                        h("p", "The art work is presented in the form of interactive, multi-user, collaborative p2p web application. Application can be run on any desktop or mobile Web Browser. The audience collaboratively explores the artwork inside virtual canvas space within multi-contextual / conceptual creative layers by touching the virtual objects. These layers are not visible by default. Interaction is based on applying or viewing through some sort of \"filters\" (augmenting reality in virtual reality). Several participants can personally or collaboratively explore the hidden layers, as well as experimenting with the artwork through these layers without breaking the original artwork."),
+                        h("p", "For the implementation Open Source frameworks for Web Browser were used, especially LiveCoding.space SDK. During the development of the project there was implemented multi-user synchronization support for several open source frameworks across web browsers: multi-user 2D canvas in Two.js, synchronized Transport object in Tone.js. Also backported a video synchronization solution from Croquet V onto Virtual World Framework. MediaPipe and Hand.js were used for offline extracting of the body motion from the video performance. The project can be built from the source code and run locally without the need of internet connection, as it has no dependencies on any cloud services. The source code will be available on the project site soon.")
+                    ])
+                ]),
+                h('main', {}, [
+                    h(self.Card, { url: "https://localhost:3007/" + self.userName + "/" + self.worldName }),
+                    h(For, { each: () => state.instances }, (instance) =>
+                        h(self.CardInstance, { url: instance.url, clients: instance.clients, instanceID: instance.instanceID })
+                    )
+
+                ]),
+                h('footer', {
+                    style: {
+                        "margin-top": "auto"
+                    }
+                }, [
+
+                    h("small", { style: { color: "var(--sl-color-gray-500" } }, [
+                        "Made with ",
+                        h("a", { class: "link-in-text", href: "https://livecoding.space" }, "LiveCoding.space"), //type: "text", size:"medium",
+                        " 2021"
+                    ])
+                ])
+
+            ]
+
+        };
+
+        render(App, document.body); //document.getElementById("root")
+
+    }
+
+    initReflectorConnection(fun) {
+        let self = this;
+        this.options = {
+
+            query: 'pathname=' + window.location.pathname.slice(1,
+                window.location.pathname.lastIndexOf("/")),
+            secure: window.location.protocol === "https:",
+            reconnection: false,
+            path: '',
+            transports: ['websocket']
+        }
+
+        //window.location.host
+        var socket = io.connect(window._app.reflectorHost, this.options);
+
+        const parse = (msg) => {
+            //self.setCount(msg)
+            //fun("instance", msg)
+            this.parseOnlineData(fun, msg)
+        }
+        socket.on('getWebAppUpdate', msg => parse.call(this, msg));
+        socket.on("connect", function () {
+
+            let noty = new Noty({
+                text: 'Connected to Reflector!',
+                timeout: 2000,
+                theme: 'mint',
+                layout: 'bottomRight',
+                type: 'success'
+            });
+            noty.show();
+        })
+
+        socket.on('connect_error', function (err) {
+            console.log(err);
+            var errDiv = document.createElement("div");
+            errDiv.innerHTML = "<div class='vwf-err' style='z-index: 10; position: absolute; top: 80px; right: 50px'>Connection error to Reflector!" + err + "</div>";
+            document.querySelector('body').appendChild(errDiv);
+
+            let noty = new Noty({
+                text: 'Connection error to Reflector! ' + err,
+                theme: 'mint',
+                layout: 'bottomRight',
+                type: 'error'
+            });
+            noty.show();
+
+        });
+
+    }
+
+
+    parseOnlineData(fun, data) {
+        let self = this;
+
+        let parcedData = _app.parseAppInstancesData(data);
+        let worldInfo = parcedData[self.worldName];
+        if (worldInfo) {
+            const instances = Object.entries(worldInfo).map(el => {
+                let url = el[0].split('/');
+                return {
+                    clients: el[1].clients,
+                    user: el[1].user,
+                    url: window.location.href + el[1].user + '/' + url[1] + '?k=' + url[3],
+                    instanceID: url[3]
+                }
+            });
+            fun("instances", instances);
+        } else {
+            fun("instances", []);
+        }
+        //this.initView(Object.values(worldInfo))
+    }
+
+}
+
+export { Standalone }