Procházet zdrojové kódy

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe před 7 roky
rodič
revize
ef5ae3aa5c

binární
Viewer/assets/img/close.png


binární
Viewer/assets/img/help-circle.png


+ 14 - 1
Viewer/assets/templates/default/defaultTemplate.html

@@ -24,7 +24,20 @@
         width: 100%;
         height: 100%;
     }
+
+    overlay {
+        position: absolute;
+        z-index: 99;
+        opacity: 0;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        -webkit-transition: opacity 1s ease;
+        -moz-transition: opacity 1s ease;
+        transition: opacity 1s ease;
+    }
 </style>
 
 <viewer></viewer>
-<loading-screen></loading-screen>
+<loading-screen></loading-screen>
+<overlay></overlay>

+ 1 - 0
Viewer/assets/templates/default/error.html

@@ -0,0 +1 @@
+Error loading the model

+ 1 - 0
Viewer/assets/templates/default/help.html

@@ -0,0 +1 @@
+HELP

+ 16 - 0
Viewer/assets/templates/default/navbar.html

@@ -46,6 +46,22 @@
     }
 </style>
 
+{{#if disableOnFullscreen}}
+<style>
+    viewer:fullscreen nav-bar {
+        display: none;
+    }
+
+    viewer:-moz-full-screen nav-bar {
+        display: none;
+    }
+
+    viewer:-webkit-full-screen nav-bar {
+        display: none;
+    }
+</style>
+{{/if}}
+
 <div class="flex-container" id="model-metadata">
     <!-- holding the description -->
     <div class="thumbnail">

+ 34 - 0
Viewer/assets/templates/default/overlay.html

@@ -0,0 +1,34 @@
+<style>
+    .overlay {
+        width: 100%;
+        height: 100%;
+        display: none;
+        align-items: center;
+        justify-content: center;
+        background-color: rgba(121, 121, 121, 0.3);
+    }
+
+    error.overlay {
+        background-color: rgba(121, 121, 121, 1);
+    }
+
+    div#close-button {
+        position: absolute;
+        top: 10px;
+        right: 10px;
+        width: 30px;
+        height: 30px;
+        cursor: pointer;
+    }
+
+    div#close-button img {
+        width: 100%;
+    }
+</style>
+
+<div id="close-button">
+    <img src="{{closeImage}}" alt="{{closeText}}">
+</div>
+<help class="overlay"></help>
+<error class="overlay"></error>
+<share class="overlay"></share>

+ 1 - 0
Viewer/assets/templates/default/share.html

@@ -0,0 +1 @@
+SHARE

+ 1 - 1
Viewer/dist/basicExample.html

@@ -18,7 +18,7 @@
 
     <body>
         <babylon model.title="Amazing Rabbit" model.subtitle="BabylonJS" model.thumbnail="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png"
-            model.url="https://playground.babylonjs.com/scenes/Rabbit.babylon" default-viewer="true"></babylon>
+            model.url="https://playground.babylonjs.com/scenes/Rabbit.babylon" default-viewer="true" templates.nav-bar.params.disable-on-fullscreen="true"></babylon>
         <script src="viewer.js"></script>
     </body>
 

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 236 - 105
Viewer/dist/viewer.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 60 - 31
Viewer/src/configuration/configuration.ts


+ 80 - 29
Viewer/src/templateManager.ts

@@ -1,13 +1,12 @@
 
 import { Observable } from 'babylonjs';
-import { isUrl, loadFile, camelToKebab } from './helper';
+import { isUrl, loadFile, camelToKebab, kebabToCamel } from './helper';
 
-
-export interface TemplateConfiguration {
+export interface ITemplateConfiguration {
     location?: string; // #template-id OR http://example.com/loading.html
     html?: string; // raw html string
     id?: string;
-    config?: { [key: string]: string | number | boolean | object };
+    params?: { [key: string]: string | number | boolean | object };
     events?: {
         // pointer events
         pointerdown?: boolean | Array<string>;
@@ -26,7 +25,6 @@ export interface TemplateConfiguration {
 
         [key: string]: boolean | Array<string> | undefined;
     }
-    children?: { [name: string]: TemplateConfiguration };
 }
 
 export interface EventCallback {
@@ -54,18 +52,18 @@ export class TemplateManager {
         this.onAllLoaded = new Observable<TemplateManager>();
     }
 
-    public initTemplate(configuration: TemplateConfiguration, name: string = 'main', parentTemplate?: Template) {
-        //init template
-        let template = new Template(name, configuration);
-        this.templates[name] = template;
+    public initTemplate(templates: { [key: string]: ITemplateConfiguration }) {
 
-        let childrenMap = configuration.children || {};
-        let childrenTemplates = Object.keys(childrenMap).map(name => {
-            return this.initTemplate(childrenMap[name], name, template);
-        });
+        let internalInit = (dependencyMap, name: string, parentTemplate?: Template) => {
+            //init template
+            let template = this.templates[name];
 
-        // register the observers
-        template.onLoaded.add(() => {
+            let childrenTemplates = Object.keys(dependencyMap).map(childName => {
+                return internalInit(dependencyMap[childName], childName, template);
+            });
+
+            // register the observers
+            //template.onLoaded.add(() => {
             let addToParent = () => {
                 let containingElement = parentTemplate && parentTemplate.parent.querySelector(camelToKebab(name)) || this.containerElement;
                 template.appendTo(containingElement);
@@ -79,9 +77,47 @@ export class TemplateManager {
             } else {
                 addToParent();
             }
+            //});
+
+            return template;
+        }
+
+        //build the html tree
+        let htmlTree = this.buildHTMLTree(templates).then(htmlTree => {
+            internalInit(htmlTree, 'main');
+        });
+    }
+
+    /**
+     * 
+     * This function will create a simple map with child-dependencies of the template html tree.
+     * It will compile each template, check if its children exist in the configuration and will add them if they do.
+     * It is expected that the main template will be called main!
+     * 
+     * @private
+     * @param {{ [key: string]: ITemplateConfiguration }} templates 
+     * @memberof TemplateManager
+     */
+    private buildHTMLTree(templates: { [key: string]: ITemplateConfiguration }): Promise<object> {
+        let promises = Object.keys(templates).map(name => {
+            let template = new Template(name, templates[name]);
+            this.templates[name] = template;
         });
 
-        return template;
+        return Promise.all(promises).then(() => {
+            let templateStructure = {};
+            // now iterate through all templates and check for children:
+            let buildTree = (parentObject, name) => {
+                let childNodes = this.templates[name].getChildElements().filter(n => !!this.templates[n]);
+                childNodes.forEach(element => {
+                    parentObject[element] = {};
+                    buildTree(parentObject[element], element);
+                });
+            }
+
+            buildTree(templateStructure, "main");
+            return templateStructure;
+        });
     }
 
     // assumiung only ONE(!) canvas
@@ -120,9 +156,11 @@ export class Template {
 
     public parent: HTMLElement;
 
+    public initPromise: Promise<Template>;
+
     private fragment: DocumentFragment;
 
-    constructor(public name: string, private configuration: TemplateConfiguration) {
+    constructor(public name: string, private _configuration: ITemplateConfiguration) {
         this.onInit = new Observable<Template>();
         this.onLoaded = new Observable<Template>();
         this.onAppended = new Observable<Template>();
@@ -137,28 +175,41 @@ export class Template {
         */
         this.onInit.notifyObservers(this);
 
-        let htmlContentPromise = getTemplateAsHtml(configuration);
+        let htmlContentPromise = getTemplateAsHtml(_configuration);
 
-        htmlContentPromise.then(htmlTemplate => {
+        this.initPromise = htmlContentPromise.then(htmlTemplate => {
             if (htmlTemplate) {
                 let compiledTemplate = Handlebars.compile(htmlTemplate);
-                let config = this.configuration.config || {};
+                let config = this._configuration.params || {};
                 let rawHtml = compiledTemplate(config);
                 this.fragment = document.createRange().createContextualFragment(rawHtml);
                 this.isLoaded = true;
                 this.onLoaded.notifyObservers(this);
             }
+            return this;
         });
     }
 
+    public get configuration(): ITemplateConfiguration {
+        return this._configuration;
+    }
+
+    public getChildElements(): Array<string> {
+        let childrenArray: string[] = [];
+        for (let i = 0; i < this.fragment.children.length; ++i) {
+            childrenArray.push(kebabToCamel(this.fragment.children.item(i).nodeName.toLowerCase()));
+        }
+        return childrenArray;
+    }
+
     public appendTo(parent: HTMLElement) {
         if (this.parent) {
-            console.error('Alread appanded to ', this.parent);
+            console.error('Already appanded to ', this.parent);
         } else {
             this.parent = parent;
 
-            if (this.configuration.id) {
-                this.parent.id = this.configuration.id;
+            if (this._configuration.id) {
+                this.parent.id = this._configuration.id;
             }
             this.parent.appendChild(this.fragment);
             // appended only one frame after.
@@ -199,18 +250,18 @@ export class Template {
 
     // TODO - Should events be removed as well? when are templates disposed?
     private registerEvents() {
-        if (this.configuration.events) {
-            Object.keys(this.configuration.events).forEach(eventName => {
-                if (this.configuration.events && this.configuration.events[eventName]) {
+        if (this._configuration.events) {
+            Object.keys(this._configuration.events).forEach(eventName => {
+                if (this._configuration.events && this._configuration.events[eventName]) {
                     let functionToFire = (selector, event) => {
                         this.onEventTriggered.notifyObservers({ event: event, template: this, selector: selector });
                     }
 
                     // if boolean, set the parent as the event listener
-                    if (typeof this.configuration.events[eventName] === 'boolean') {
+                    if (typeof this._configuration.events[eventName] === 'boolean') {
                         this.parent.addEventListener(eventName, functionToFire.bind(this, '#' + this.parent.id), false);
                     } else {
-                        let selectorsArray: Array<string> = <Array<string>>this.configuration.events[eventName];
+                        let selectorsArray: Array<string> = <Array<string>>this._configuration.events[eventName];
                         selectorsArray.forEach(selector => {
                             let htmlElement = <HTMLElement>this.parent.querySelector(selector);
                             htmlElement && htmlElement.addEventListener(eventName, functionToFire.bind(this, selector), false)
@@ -223,7 +274,7 @@ export class Template {
 
 }
 
-export function getTemplateAsHtml(templateConfig: TemplateConfiguration): Promise<string> {
+export function getTemplateAsHtml(templateConfig: ITemplateConfiguration): Promise<string> {
     if (!templateConfig) {
         return Promise.reject('No templateConfig provided');
     } else if (templateConfig.html) {

+ 101 - 41
Viewer/src/viewer/defaultViewer.ts

@@ -28,43 +28,55 @@ export class DefaultViewer extends AbstractViewer {
         let navbarHeight = navbar.parent.clientHeight + 'px';
 
         let navbarShown: boolean = true;
+        let timeoutCancel /*: number*/;
+
+        let triggerNavbar = function (show: boolean = false, evt: PointerEvent) {
+            // only left-click on no-button.
+            if (evt.button > 0) return;
+            // clear timeout
+            timeoutCancel && clearTimeout(timeoutCancel);
+            // if state is the same, do nothing
+            if (show === navbarShown) return;
+            //showing? simply show it!
+            if (show) {
+                navbar.parent.style.bottom = show ? '0px' : '-' + navbarHeight;
+                navbarShown = show;
+            } else {
+                let visibilityTimeout = 2000;
+                if (navbar.configuration.params && navbar.configuration.params.visibilityTimeout !== undefined) {
+                    visibilityTimeout = <number>navbar.configuration.params.visibilityTimeout;
+                }
+                // not showing? set timeout until it is removed.
+                timeoutCancel = setTimeout(function () {
+                    navbar.parent.style.bottom = '-' + navbarHeight;
+                    navbarShown = show;
+                }, visibilityTimeout);
+            }
+        }
 
-        viewerElement.parent.addEventListener('pointerout', () => {
-            if (navbarShown) return;
-            navbar.parent.style.bottom = '0px';
-            navbarShown = true;
-        });
-
-        let timeoutCancel;
-
-        viewerElement.parent.addEventListener('pointerdown', () => {
-            if (!navbarShown) return;
-            navbar.parent.style.bottom = '-' + navbarHeight;
-            navbarShown = false;
 
-            if (timeoutCancel) {
-                clearTimeout(timeoutCancel);
-            }
-        });
 
-        viewerElement.parent.addEventListener('pointerup', () => {
-            if (timeoutCancel) {
-                clearTimeout(timeoutCancel);
-            }
+        viewerElement.parent.addEventListener('pointerout', triggerNavbar.bind(this, false));
+        viewerElement.parent.addEventListener('pointerdown', triggerNavbar.bind(this, true));
+        viewerElement.parent.addEventListener('pointerup', triggerNavbar.bind(this, false));
+        navbar.parent.addEventListener('pointerover', triggerNavbar.bind(this, true))
+        // triggerNavbar(false);
 
-            timeoutCancel = setTimeout(() => {
-                navbar.parent.style.bottom = '0px';
-                navbarShown = true;
-            }, 2000)
-        });
+        // close overlay button
+        let closeButton = document.getElementById('close-button');
+        if (closeButton) {
+            closeButton.addEventListener('pointerdown', () => {
+                this.hideOverlayScreen();
+            })
+        }
 
         // events registration
-        this.registerFullscreenMode();
+        this.registerNavbarButtons();
 
         return super.onTemplatesLoaded();
     }
 
-    private registerFullscreenMode() {
+    private registerNavbarButtons() {
         let isFullscreen = false;
 
         let navbar = this.templateManager.getTemplate('navBar');
@@ -75,20 +87,22 @@ export class DefaultViewer extends AbstractViewer {
                 case 'pointerdown':
                     let event: PointerEvent = <PointerEvent>data.event;
                     if (event.button === 0) {
-                        if (data.selector === '#fullscreen-button') {
-                            //this.engine.switchFullscreen(false);
-                            if (!isFullscreen) {
-                                let requestFullScreen = viewerElement.requestFullscreen || /*viewerElement.parent.msRequestFullscreen || viewerElement.parent.mozRequestFullScreen ||*/ viewerElement.webkitRequestFullscreen;
-                                requestFullScreen.call(viewerElement);
-                            } else {
-                                let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
-                                exitFullscreen.call(document);
-                            }
-
-                            isFullscreen = !isFullscreen;
-
+                        switch (data.selector) {
+                            case '#fullscreen-button':
+                                if (!isFullscreen) {
+                                    let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen; // || viewerElement.parent.msRequestFullscreen || viewerElement.parent.mozRequestFullScreen 
+                                    requestFullScreen.call(viewerElement);
+                                } else {
+                                    let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
+                                    exitFullscreen.call(document);
+                                }
+
+                                isFullscreen = !isFullscreen;
+                                break;
+                            case '#help-button':
+                                this.showOverlayScreen('help');
+                                break;
                         }
-
                     }
                     break;
             }
@@ -103,7 +117,10 @@ export class DefaultViewer extends AbstractViewer {
 
     public loadModel(model: any = this.configuration.model): Promise<Scene> {
         this.showLoadingScreen();
-        return super.loadModel(model, true);
+        return super.loadModel(model, true).catch(() => {
+            this.showOverlayScreen('error');
+            return this.scene;
+        });
     }
 
     public onModelLoaded(meshes: Array<AbstractMesh>) {
@@ -207,6 +224,49 @@ export class DefaultViewer extends AbstractViewer {
         return Promise.resolve(this.scene);
     }
 
+    public showOverlayScreen(subScreen: string) {
+        return this.templateManager.getTemplate('overlay').show((template => {
+
+            var canvasRect = this.containerElement.getBoundingClientRect();
+            var canvasPositioning = window.getComputedStyle(this.containerElement).position;
+
+            template.parent.style.display = 'flex';
+            template.parent.style.width = canvasRect.width + "px";
+            template.parent.style.height = canvasRect.height + "px";
+            template.parent.style.opacity = "1";
+
+            return this.templateManager.getTemplate(subScreen).show((template => {
+                template.parent.style.display = 'flex';
+                return Promise.resolve(template);
+            }));
+        }));
+    }
+
+    public hideOverlayScreen() {
+        return this.templateManager.getTemplate('overlay').hide((template => {
+            template.parent.style.opacity = "0";
+            let onTransitionEnd = () => {
+                template.parent.removeEventListener("transitionend", onTransitionEnd);
+                template.parent.style.display = 'none';
+            }
+            template.parent.addEventListener("transitionend", onTransitionEnd);
+
+            let overlays = template.parent.querySelectorAll('.overlay');
+            if (overlays) {
+                for (let i = 0; i < overlays.length; ++i) {
+                    let htmlElement = <HTMLElement>overlays.item(i);
+                    htmlElement.style.display = 'none';
+                }
+            }
+
+            /*return this.templateManager.getTemplate(subScreen).show((template => {
+                template.parent.style.display = 'none';
+                return Promise.resolve(template);
+            }));*/
+            return Promise.resolve(template);
+        }));
+    }
+
     public showLoadingScreen() {
         return this.templateManager.getTemplate('loadingScreen').show((template => {
 

+ 2 - 2
Viewer/src/viewer/viewer.ts

@@ -29,12 +29,12 @@ export abstract class AbstractViewer {
         configurationLoader.loadConfiguration(initialConfiguration).then((configuration) => {
             this.configuration = configuration;
             // initialize the templates
-            let templateConfiguration = this.configuration.template || {};
+            let templateConfiguration = this.configuration.templates || {};
             this.templateManager.initTemplate(templateConfiguration);
             // when done, execute onTemplatesLoaded()
             this.templateManager.onAllLoaded.add(() => {
                 this.onTemplatesLoaded();
-            })
+            });
         });
 
     }