Parcourir la source

language support

David `Deltakosh` Catuhe il y a 5 ans
Parent
commit
5fc4c5796c

+ 8 - 0
Playground/js.html

@@ -0,0 +1,8 @@
+<script>
+    localStorage.setItem("language", "JS");
+    
+    var parseURL = window.location.href.split("js.html");
+    var hash = parseURL[parseURL.length - 1];
+
+    window.location = "./" + hash;
+</script>

+ 0 - 4
Playground/old/js.html

@@ -1,4 +0,0 @@
-<script>
-    localStorage.setItem("bjs-playground-scriptLanguage", "JS");
-    window.location = "./";
-</script>

+ 25 - 2
Playground/src/components/commandBarComponent.tsx

@@ -57,8 +57,31 @@ export class CommandBarComponent extends React.Component<ICommandBarComponentPro
                             "Light",
                             "Dark"
                         ],
-                        onClick: () => {}
-                    },  {
+                        onClick: () => {
+                            this.props.globalState.onThemeChangedObservable.notifyObservers();
+                        }
+                    },  
+                    {
+                        label: "Font size",
+                        storeKey: "font-size",
+                        defaultValue: "14",
+                        subItems: [
+                            "12",
+                            "14",
+                            "16",
+                            "18",
+                            "20",
+                            "22",
+                            "24",
+                            "26",
+                            "28",
+                            "30",
+                        ],
+                        onClick: () => {
+                            this.props.globalState.onFontSizeChangedObservable.notifyObservers();
+                        }
+                    },
+                    {
                         label: "Safe mode",
                         storeKey: "safe-mode",
                         defaultValue: false,

+ 16 - 7
Playground/src/components/commandDropdownComponent.tsx

@@ -43,15 +43,17 @@ export class CommandDropdownComponent extends React.Component<ICommandDropdownCo
                                     this.props.items.map(m => {
                                         return (
                                             <div className="command-dropdown-label" key={m.label} onClick={() => {
-                                                if (! m.onClick) {
+                                                if (!m.onClick) {
                                                     let newValue = !Utilities.ReadBoolFromStore(m.storeKey!, (m.defaultValue as boolean) || false);
-                                                    Utilities.StoreBoolFromStore(m.storeKey!, newValue);
+                                                    Utilities.StoreBoolToStore(m.storeKey!, newValue);
                                                     this.forceUpdate();
                                                     m.onCheck!(newValue);
                                                     return;
                                                 }
-                                                m.onClick();
-                                                this.setState({isExpanded: false});
+                                                if (!m.subItems) {
+                                                    m.onClick();
+                                                    this.setState({isExpanded: false});
+                                                }
                                             }} title={m.label}>
                                                 <div className="command-dropdown-label-text">
                                                     {m.label}
@@ -60,7 +62,7 @@ export class CommandDropdownComponent extends React.Component<ICommandDropdownCo
                                                     m.onCheck && 
                                                     <input type="checkBox" className="command-dropdown-label-check" 
                                                         onChange={(evt) => {
-                                                            Utilities.StoreBoolFromStore(m.storeKey!, evt.target.checked);
+                                                            Utilities.StoreBoolToStore(m.storeKey!, evt.target.checked);
                                                             this.forceUpdate();
                                                             m.onCheck!(evt.target.checked);
                                                         }}
@@ -72,9 +74,16 @@ export class CommandDropdownComponent extends React.Component<ICommandDropdownCo
                                                         {
                                                             m.subItems.map(s => {
                                                                 return (
-                                                                    <div key={s} className="sub-item">
-                                                                        {s}
+                                                                    <div key={s} className={"sub-item" + (Utilities.ReadStringFromStore(m.storeKey!, m.defaultValue as string) === s ? " checked" : "")}  
+                                                                    onClick={() => {
+                                                                        Utilities.StoreStringToStore(m.storeKey!, s);                                                                        
+                                                                        m.onClick!();
+                                                                        this.setState({isExpanded: false});
+                                                                    }}>
+                                                                        <div className="sub-item-label">
+                                                                            {s}
                                                                         </div>
+                                                                    </div>
                                                                 )
                                                             })
                                                         }

+ 57 - 0
Playground/src/components/errorDisplayComponent.tsx

@@ -0,0 +1,57 @@
+import * as React from "react";
+import { GlobalState } from '../globalState';
+import { Nullable } from 'babylonjs/types';
+
+require("../scss/errorDisplay.scss");
+
+interface IErrorDisplayComponentProps {
+    globalState: GlobalState;
+}
+
+export class CompilationError {
+    message: string;
+    lineNumber?: number;
+    columnNumber?: number;
+}
+
+export class ErrorDisplayComponent extends React.Component<IErrorDisplayComponentProps, {error: Nullable<CompilationError>}> {    
+    
+    public constructor(props: IErrorDisplayComponentProps) {
+        super(props);
+
+        this.state = {error: null};
+
+        this.props.globalState.onErrorObservable.add((err) => {
+            this.setState({error: err});
+        });
+    }
+
+    private _onClick() {
+        if (this.state.error && this.state.error.lineNumber && this.state.error.columnNumber) {
+            const position = {
+                lineNumber: this.state.error.lineNumber,
+                column: this.state.error.columnNumber
+            };
+
+            this.props.globalState.onNavigateRequiredObservable.notifyObservers(position);
+        }
+        this.setState({error: null});
+    }
+
+    public render() {
+
+        if (!this.state.error) {
+            return null;
+        }
+
+        return (
+            <div className="error-display" onClick={() => this._onClick()}>
+                {
+                    this.state.error.lineNumber && this.state.error.columnNumber &&
+                    `Error at [${this.state.error.lineNumber}, ${this.state.error.columnNumber}]: `
+                }
+                {this.state.error.message}
+            </div>
+        )
+    }
+}

+ 4 - 0
Playground/src/components/footerComponent.tsx

@@ -18,6 +18,10 @@ export class FooterComponent extends React.Component<IFooterComponentProps> {
     public constructor(props: IFooterComponentProps) {
         super(props);
         this._fpsRef = React.createRef();
+
+        this.props.globalState.onLanguageChangedObservable.add(() => {
+            this.forceUpdate();
+        });
     }
     
     componentDidMount() {       

+ 11 - 2
Playground/src/components/headerComponent.tsx

@@ -4,6 +4,7 @@ import { GlobalState } from '../globalState';
 import LogoImage from "../imgs/logo.svg";
 import { Engine } from 'babylonjs/Engines/engine';
 import { CommandBarComponent } from './commandBarComponent';
+import { Utilities } from '../tools/utilities';
 
 require("../scss/header.scss");
 
@@ -18,6 +19,10 @@ export class HeaderComponent extends React.Component<IHeaderComponentProps> {
         super(props);
 
         this._refVersionNumber = React.createRef();
+
+        this.props.globalState.onLanguageChangedObservable.add(() => {
+            this.forceUpdate();
+        });
     }
 
     componentDidMount() {
@@ -35,14 +40,18 @@ export class HeaderComponent extends React.Component<IHeaderComponentProps> {
                     {
                         this.props.globalState.language === "JS" &&
                         <>                        
-                            <div className="language-button active background-ts">TS</div>
+                            <div className="language-button active background-ts" onClick={() => {
+                                Utilities.SwitchLanguage("TS", this.props.globalState);
+                            }}>TS</div>
                             <div className="language-button background-js">Javascript</div>
                         </>
                     }
                     {
                         this.props.globalState.language === "TS" &&
                         <>                        
-                            <div className="language-button active background-js">JS</div>
+                            <div className="language-button active background-js" onClick={() => {
+                                Utilities.SwitchLanguage("JS", this.props.globalState);                             
+                            }}>JS</div>
                             <div className="language-button background-ts">TypeScript</div>
                         </>
                     }                    

+ 135 - 130
Playground/src/components/rendererComponent.tsx

@@ -26,10 +26,11 @@ export class RenderingComponent extends React.Component<IRenderingComponentProps
         // Create the global handleException
         (window as any).handleException = (e: Error) => {
             console.error(e);
+            this.props.globalState.onErrorObservable.notifyObservers(e);
         }
 
         this.props.globalState.onRunRequiredObservable.add(() => {
-           this.compileAndRun();
+           this._compileAndRunAsync();
         });
 
         
@@ -61,8 +62,9 @@ export class RenderingComponent extends React.Component<IRenderingComponentProps
 
     }
 
-    compileAndRun() {
-        this.props.globalState.onDisplayWaitRingObservable.notifyObservers(false);
+    private async _compileAndRunAsync() {
+        this.props.globalState.onDisplayWaitRingObservable.notifyObservers(false);        
+        this.props.globalState.onErrorObservable.notifyObservers(null);
 
         if (this._engine) {
             try {
@@ -74,152 +76,155 @@ export class RenderingComponent extends React.Component<IRenderingComponentProps
             this._engine = null;
         }   
 
-        let globalObject = window as any;
-        let canvas = this._canvasRef.current!;
-        globalObject.canvas = canvas;
-        
-        globalObject.createDefaultEngine = function () {
-            return new Engine(canvas, true, {
-                preserveDrawingBuffer: true,
-                stencil: true
-            });
-        }
+        try {
+            let globalObject = window as any;
+            let canvas = this._canvasRef.current!;
+            globalObject.canvas = canvas;
+            
+            globalObject.createDefaultEngine = function () {
+                return new Engine(canvas, true, {
+                    preserveDrawingBuffer: true,
+                    stencil: true
+                });
+            }
 
-        let zipVariables = "var engine = null;\r\nvar scene = null;\r\nvar sceneToRender = null;\r\n";
-        let defaultEngineZip = "var createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true }); }";
-        let code = this.props.globalState.currentCode;        
-        let createEngineFunction = "createDefaultEngine";
-        let createSceneFunction = "";
-        let checkCamera = true;
-        let checkSceneCount = true;
+            let zipVariables = "var engine = null;\r\nvar scene = null;\r\nvar sceneToRender = null;\r\n";
+            let defaultEngineZip = "var createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true }); }";
+            let code = await this.props.globalState.getCompiledCode();        
+            let createEngineFunction = "createDefaultEngine";
+            let createSceneFunction = "";
+            let checkCamera = true;
+            let checkSceneCount = true;
 
-        if (code.indexOf("createEngine") !== -1) {
-            createEngineFunction = "createEngine";
-        }
-
-        // Check for different typos
-        if (code.indexOf("delayCreateScene") !== -1) { // delayCreateScene
-            createSceneFunction = "delayCreateScene";
-            checkCamera = false;
-        } else if (code.indexOf("createScene") !== -1) { // createScene
-            createSceneFunction = "createScene";
-        } else if (code.indexOf("CreateScene") !== -1) { // CreateScene
-            createSceneFunction = "CreateScene";
-        } else if (code.indexOf("createscene") !== -1) { // createscene
-            createSceneFunction = "createscene";
-        }
+            if (code.indexOf("createEngine") !== -1) {
+                createEngineFunction = "createEngine";
+            }
 
-        if (!createSceneFunction) {
-            this._engine = globalObject.createDefaultEngine() as Engine;
-            this._scene = new Scene(this._engine);
-
-            globalObject.engine = this._engine;
-            globalObject.scene = this._scene;
-
-            let runScript:any = null;
-            Utilities.FastEval("runScript = function(scene, canvas) {" + code + "}");
-            runScript(this._scene, canvas);            
-
-            this.props.globalState.zipCode = zipVariables + defaultEngineZip + "var engine = createDefaultEngine();" + ";\r\nvar scene = new BABYLON.Scene(engine);\r\n\r\n" + code;
-        } else {
-            code += `
-var engine;
-try {
-engine = ${createEngineFunction}();
-} catch(e) {
-console.log("the available createEngine function failed. Creating the default engine instead");
-engine = createDefaultEngine();
-}`;
-            code += "\r\nif (!engine) throw 'engine should not be null.';";
-
-            if (this.props.globalState.language === "JS") {
-                code += "\r\n" + "scene = " + createSceneFunction + "();";
-            } else {
-                var startCar = code.search('var ' + createSceneFunction);
-                code = code.substr(0, startCar) + code.substr(startCar + 4);
-                code += "\n" + "scene = " + createSceneFunction + "();";
+            // Check for different typos
+            if (code.indexOf("delayCreateScene") !== -1) { // delayCreateScene
+                createSceneFunction = "delayCreateScene";
+                checkCamera = false;
+            } else if (code.indexOf("createScene") !== -1) { // createScene
+                createSceneFunction = "createScene";
+            } else if (code.indexOf("CreateScene") !== -1) { // CreateScene
+                createSceneFunction = "CreateScene";
+            } else if (code.indexOf("createscene") !== -1) { // createscene
+                createSceneFunction = "createscene";
             }
 
-            // Execute the code
-            Utilities.FastEval(code);
+            if (!createSceneFunction) {
+                this._engine = globalObject.createDefaultEngine() as Engine;
+                this._scene = new Scene(this._engine);
 
-            this._engine = globalObject.engine;
+                globalObject.engine = this._engine;
+                globalObject.scene = this._scene;
 
-            if (!this._engine) {
-                this.props.globalState.onErrorObservable.notifyObservers("createEngine function must return an engine.");
-                return;
-            }
+                let runScript:any = null;
+                Utilities.FastEval("runScript = function(scene, canvas) {" + code + "}");
+                runScript(this._scene, canvas);            
 
-            if (!globalObject.scene) {
-                this.props.globalState.onErrorObservable.notifyObservers(createSceneFunction + " function must return a scene.");
-                return;
+                this.props.globalState.zipCode = zipVariables + defaultEngineZip + "var engine = createDefaultEngine();" + ";\r\nvar scene = new BABYLON.Scene(engine);\r\n\r\n" + code;
+            } else {
+                code += `
+    var engine;
+    try {
+    engine = ${createEngineFunction}();
+    } catch(e) {
+    console.log("the available createEngine function failed. Creating the default engine instead");
+    engine = createDefaultEngine();
+    }`;
+                code += "\r\nif (!engine) throw 'engine should not be null.';";
+
+                if (this.props.globalState.language === "JS") {
+                    code += "\r\n" + "scene = " + createSceneFunction + "();";
+                } else {
+                    var startCar = code.search('var ' + createSceneFunction);
+                    code = code.substr(0, startCar) + code.substr(startCar + 4);
+                    code += "\n" + "scene = " + createSceneFunction + "();";
+                }
+
+                // Execute the code
+                Utilities.FastEval(code);
+
+                this._engine = globalObject.engine;
+
+                if (!this._engine) {
+                    this.props.globalState.onErrorObservable.notifyObservers({message: "createEngine function must return an engine."});
+                    return;
+                }
+
+                if (!globalObject.scene) {
+                    this.props.globalState.onErrorObservable.notifyObservers({message: createSceneFunction + " function must return a scene."});
+                    return;
+                }
+
+                let sceneToRenderCode = 'sceneToRender = scene';
+
+                // if scene returns a promise avoid checks
+                if (globalObject.scene.then) {
+                    checkCamera = false;
+                    checkSceneCount = false;
+                    sceneToRenderCode = 'scene.then(returnedScene => { sceneToRender = returnedScene; });\r\n';
+                } 
+
+                let createEngineZip = (createEngineFunction === "createEngine") ?
+                    zipVariables :
+                    zipVariables + defaultEngineZip;
+
+                    this.props.globalState.zipCode =
+                    createEngineZip + ";\r\n" +
+                    code + ";\r\n" +
+                    sceneToRenderCode;
             }
 
-            let sceneToRenderCode = 'sceneToRender = scene';
-
-            // if scene returns a promise avoid checks
             if (globalObject.scene.then) {
-                checkCamera = false;
-                checkSceneCount = false;
-                sceneToRenderCode = 'scene.then(returnedScene => { sceneToRender = returnedScene; });\r\n';
-            } 
-
-            let createEngineZip = (createEngineFunction === "createEngine") ?
-                zipVariables :
-                zipVariables + defaultEngineZip;
-
-                this.props.globalState.zipCode =
-                createEngineZip + ";\r\n" +
-                code + ";\r\n" +
-                sceneToRenderCode;
-        }
+                globalObject.scene.then((s : Scene) => {
+                    this._scene = s;
+                    globalObject.scene = this._scene;
+                });
+            } else {
+                this._scene = globalObject.scene as Scene;
+            }
 
-        if (globalObject.scene.then) {
-            globalObject.scene.then((s : Scene) => {
-                this._scene = s;
-                globalObject.scene = this._scene;
+            this._engine.runRenderLoop(() => {
+                if (!this._scene || !this._engine) {
+                    return;
+                }
+
+                if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
+                    this._engine.resize();
+                }
+
+                if (this._scene.activeCamera || this._scene.activeCameras.length > 0) {
+                    this._scene.render();
+                }
+
+                // Update FPS if camera is not a webxr camera
+                if(!(this._scene.activeCamera && 
+                    this._scene.activeCamera.getClassName && 
+                    this._scene.activeCamera.getClassName() === 'WebXRCamera')) {
+                    this.props.globalState.fpsElement.innerHTML = this._engine.getFps().toFixed() + " fps";
+                }
             });
-        } else {
-            this._scene = globalObject.scene as Scene;
-        }
 
-        this._engine.runRenderLoop(() => {
-            if (!this._scene || !this._engine) {
+            if (checkSceneCount && this._engine.scenes.length === 0) {
+                this.props.globalState.onErrorObservable.notifyObservers({message: "You must at least create a scene."});
                 return;
             }
 
-            if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
-                this._engine.resize();
-            }
-
-            if (this._scene.activeCamera || this._scene.activeCameras.length > 0) {
-                this._scene.render();
-            }
-
-            // Update FPS if camera is not a webxr camera
-            if(!(this._scene.activeCamera && 
-                this._scene.activeCamera.getClassName && 
-                this._scene.activeCamera.getClassName() === 'WebXRCamera')) {
-                this.props.globalState.fpsElement.innerHTML = this._engine.getFps().toFixed() + " fps";
+            if (checkCamera && this._engine.scenes[0].activeCamera == null) {
+                this.props.globalState.onErrorObservable.notifyObservers({message: "You must at least create a camera."});
+                return;
+            } else if (globalObject.scene.then) {
+                globalObject.scene.then(function () {
+                });
+            } else {
+                this._engine.scenes[0].executeWhenReady(function () {
+                });
             }
-        });
-
-        if (checkSceneCount && this._engine.scenes.length === 0) {
-            this.props.globalState.onErrorObservable.notifyObservers("You must at least create a scene.");
-            return;
+        } catch (err) {
+            this.props.globalState.onErrorObservable.notifyObservers(err);
         }
-
-        if (checkCamera && this._engine.scenes[0].activeCamera == null) {
-            this.props.globalState.onErrorObservable.notifyObservers("You must at least create a camera.");
-            return;
-        } else if (globalObject.scene.then) {
-            globalObject.scene.then(function () {
-            });
-        } else {
-            this._engine.scenes[0].executeWhenReady(function () {
-            });
-        }
-
     }
 
     public render() {

+ 10 - 2
Playground/src/globalState.ts

@@ -1,4 +1,7 @@
 import { Observable } from 'babylonjs/Misc/observable';
+import { Utilities } from './tools/utilities';
+import { CompilationError } from './components/errorDisplayComponent';
+import { Nullable } from 'babylonjs/types';
 
 export enum EditionMode {
     Desktop,
@@ -11,7 +14,8 @@ export class GlobalState {
     public readonly SnippetServerUrl = "https://snippet.babylonjs.com";
 
     public currentCode: string;
-    public language: "JS" | "TS" = "JS";
+    public getCompiledCode: () => Promise<string>;
+    public language = Utilities.ReadStringFromStore("language", "JS");;
     public fpsElement: HTMLDivElement;
     public mobileDefaultMode = EditionMode.RenderingOnly;
 
@@ -27,7 +31,7 @@ export class GlobalState {
     public onNewRequiredObservable = new Observable<void>();
     public onClearRequiredObservable = new Observable<void>();
     public onSaveRequiredObservable = new Observable<void>();
-    public onErrorObservable = new Observable<string>();    
+    public onErrorObservable = new Observable<Nullable<CompilationError>>();    
     public onMobileDefaultModeChangedObservable = new Observable<void>();
     public onDisplayWaitRingObservable = new Observable<boolean>();
     public onDisplayMetadataObservable = new Observable<boolean>();    
@@ -40,6 +44,10 @@ export class GlobalState {
     public onEditorFullcreenRequiredObservable = new Observable<void>();
     public onMinimapChangedObservable = new Observable<boolean>();
     public onEditorDisplayChangedObservable = new Observable<boolean>();
+    public onThemeChangedObservable = new Observable<void>();
+    public onFontSizeChangedObservable = new Observable<void>();
+    public onLanguageChangedObservable = new Observable<void>();
+    public onNavigateRequiredObservable = new Observable<{lineNumber: number, column: number}>();
 
     public loadingCodeInProgress = false;
     public onCodeLoaded = new Observable<string>();

+ 4 - 2
Playground/src/playground.tsx

@@ -12,6 +12,7 @@ import { MetadataComponent } from './components/metadataComponent';
 import { HamburgerMenuComponent } from './components/hamburgerMenu';
 import { Utilities } from './tools/utilities';
 import { ShortcutManager } from './tools/shortcutManager';
+import { ErrorDisplayComponent } from './components/errorDisplayComponent';
 
 require("./scss/main.scss");
 const Split = require('split.js').default;
@@ -45,7 +46,7 @@ export class Playground extends React.Component<IPlaygroundProps, {errorMessage:
 
        window.addEventListener("resize", () => {
             let defaultDesktop = Utilities.ReadBoolFromStore("editor", true) ? EditionMode.Desktop : EditionMode.RenderingOnly;
-           this.setState({mode: window.innerWidth < this._globalState.MobileSizeTrigger ? this._globalState.mobileDefaultMode : defaultDesktop});
+            this.setState({mode: window.innerWidth < this._globalState.MobileSizeTrigger ? this._globalState.mobileDefaultMode : defaultDesktop});
        });
 
        this._globalState.onMobileDefaultModeChangedObservable.add(() => {
@@ -116,7 +117,8 @@ export class Playground extends React.Component<IPlaygroundProps, {errorMessage:
                     window.innerWidth < 1024 &&
                     <HamburgerMenuComponent globalState={this._globalState}/>
                 }
-                <FooterComponent globalState={this._globalState}/>                
+                <FooterComponent globalState={this._globalState}/>    
+                <ErrorDisplayComponent globalState={this._globalState}/>            
                 <WaitRingComponent globalState={this._globalState}/>
                 <MetadataComponent globalState={this._globalState}/>
             </div>   

+ 73 - 32
Playground/src/scss/commandBar.scss

@@ -1,28 +1,47 @@
+
 .commands {
     grid-row: 1;
     grid-column: 3;
     display: flex;
 
+    &.background-ts {
+        .command-button, .command-dropdown  {
+            img {
+                filter: invert(64%) sepia(78%) saturate(940%) hue-rotate(323deg) brightness(105%) contrast(103%);
+            }
+
+            &:hover, &.activated {
+                img {
+                    filter: invert(34%) sepia(21%) saturate(3832%) hue-rotate(324deg) brightness(88%) contrast(82%) !important;
+                }
+            } 
+        }
+    }
+
+    &.background-js {
+        .command-button, .command-dropdown  {
+            img {
+                filter: invert(57%) sepia(80%) saturate(2031%) hue-rotate(215deg);
+            }
+
+            &:hover, &.activated {
+                img {
+                    filter: invert(17%) !important;
+                }
+            } 
+        }
+    }
+
     .command-button {
         cursor: pointer;
         width: 55px;
 
         .command-label {
             display: none;
-        }
-        
-        img {
-            filter: invert(57%) sepia(80%) saturate(2031%) hue-rotate(215deg);
-            &.active {
-                filter: invert(100%);
-            }
-        }
+        }        
 
         &:hover {
             background-color: white;
-            img {
-                filter: invert(17%) !important;
-            }
         } 
         
         &:active {
@@ -42,18 +61,8 @@
         width: 55px;
         height: 55px;
 
-        img {
-            filter: invert(57%) sepia(80%) saturate(2031%) hue-rotate(215deg);
-            &.active {
-                filter: invert(100%);
-            }
-        }
-
         &:hover, &.activated {
             background-color: white;
-            img {
-                filter: invert(17%) !important;
-            }
         } 
         
         &:active {
@@ -72,6 +81,26 @@
         z-index: 1;
     }
 
+    &.background-js {
+        .command-dropdown-content {
+            .command-dropdown-label {
+                &:hover {
+                    background-color: #bfabff;
+                }
+            }
+        }
+    }
+
+    &.background-ts {
+        .command-dropdown-content {
+            .command-dropdown-label {
+                &:hover {
+                    background-color: #ff7656;
+                }
+            }
+        }
+    }
+
     .command-dropdown-content {
         position: absolute;
         top: 55px;
@@ -92,11 +121,10 @@
             user-select: none;
             grid-template-columns: 1fr 20px;
             grid-template-rows: 100%;
+            position: relative;
 
-            &:hover {
-                background-color: white;
-                color: black;
 
+            &:hover {
                 .sub-items {
                     display: block;
                 }
@@ -117,27 +145,40 @@
                 left: 200px;
                 top: 0;
                 width: 150px;
-                background: white;
                 display: none;        
     
                 &.background-js {
+                    background: #bfabff;
+                    .sub-item {                      
+                        &:hover {
+                            background-color: #9379e6;
+                            color: white;
+                        }
+                    }
+                }   
+
+                &.background-ts {
+                    background: #ff7656;
                     .sub-item {                      
                         &:hover {
-                            background-color: #3f3461;
+                            background-color: #e0684b;
+                            color: white;
                         }
                     }
                 }   
                                     
                 .sub-item {                      
-                    font-family: "acumin-pro-extra-condensed";
-                    color:black;
+                    font-family: "acumin-pro-extra-condensed";                    
+                    color: white;
                     padding: 5px;
                     padding-left: 10px;
                     height: 35px;
-    
-                    &:hover {
-                        color: white;
-                    } 
+                    display: grid;
+
+                    &.checked {
+                        background: white;
+                        color: black;
+                    }
                 }
             }            
         }

+ 18 - 0
Playground/src/scss/errorDisplay.scss

@@ -0,0 +1,18 @@
+.error-display {
+    position: absolute;
+    grid-column: 1 / 3;
+    grid-row: 1 / 3;
+    bottom: 0px;
+    left: 0;
+    width: 100%;
+    height: 60px;
+    background: red;
+    margin: 20px 0;
+    color: white;
+    z-index: 100;
+    display: grid;    
+    align-content: center;
+    justify-content: center;
+    user-select: none;
+    cursor: pointer;
+}

+ 29 - 9
Playground/src/scss/hamburgerMenu.scss

@@ -43,6 +43,34 @@
     opacity: 0;
     transition: all 0.2s ease;
 
+    &.background-ts {
+        .command-button, .command-dropdown  {
+            img {
+                filter: invert(64%) sepia(78%) saturate(940%) hue-rotate(323deg) brightness(105%) contrast(103%);
+            }
+
+            &:hover, &.activated {
+                img {
+                    filter: invert(34%) sepia(21%) saturate(3832%) hue-rotate(324deg) brightness(88%) contrast(82%) !important;
+                }
+            } 
+        }
+    }
+
+    &.background-js {
+        .command-button, .command-dropdown  {
+            img {
+                filter: invert(57%) sepia(80%) saturate(2031%) hue-rotate(215deg);
+            }
+
+            &:hover, &.activated {
+                img {
+                    filter: invert(17%) !important;
+                }
+            } 
+        }
+    }    
+
     &.expanded {
         transform: translateX(0);
         opacity: 1;
@@ -67,18 +95,10 @@
         img {
             grid-column: 1;
             grid-row: 1;
-            filter: invert(57%) sepia(80%) saturate(2031%) hue-rotate(215deg);
-            &.active {
-                filter: invert(100%);
-            }
         }
 
         &:hover {
-            background-color: white;
-            img {
-                filter: invert(17%) !important;
-            }
-            
+            background-color: white;            
             .command-label {
                 color: black;
             }

+ 4 - 0
Playground/src/scss/main.scss

@@ -41,6 +41,10 @@
 
 .background-ts {
     background-color: #bb464b;
+
+    .sub1 {
+        background-color:#e0684b
+    }
 }
 
 .background-js {

+ 9 - 3
Playground/src/tools/loadManager.ts

@@ -63,9 +63,15 @@ export class LoadManager {
                 if (xmlHttp.readyState === 4) {
                     if (xmlHttp.status === 200) {
 
-                        // if (!this.checkTypescriptSupport(xmlHttp)) {
-                        //     return;
-                        // }
+                        if (xmlHttp.responseText.indexOf("class Playground") !== -1) {
+                            if (this.globalState.language === "JS") {
+                                Utilities.SwitchLanguage("TS", this.globalState);
+                            }
+                        } else { // If we're loading JS content and it's TS page
+                            if (this.globalState.language === "TS") {
+                                Utilities.SwitchLanguage("JS", this.globalState);
+                            }
+                        }
 
                         var snippet = JSON.parse(xmlHttp.responseText);
 

+ 135 - 31
Playground/src/tools/monacoManager.ts

@@ -7,6 +7,7 @@ import * as languageFeatures from "monaco-editor/esm/vs/language/typescript/lang
 
 import { GlobalState } from '../globalState';
 import { Utilities } from './utilities';
+import { CompilationError } from '../components/errorDisplayComponent';
 
 declare type IStandaloneCodeEditor = import('monaco-editor/esm/vs/editor/editor.api').editor.IStandaloneCodeEditor;
 declare type IStandaloneEditorConstructionOptions = import('monaco-editor/esm/vs/editor/editor.api').editor.IStandaloneEditorConstructionOptions;
@@ -17,6 +18,7 @@ export class MonacoManager {
     private _editor: IStandaloneCodeEditor;
     private _definitionWorker: Worker;
     private _deprecatedCandidates: string[];
+    private _hostElement: HTMLDivElement;
     private _templates: {
         label: string, 
         language: string,
@@ -36,20 +38,25 @@ export class MonacoManager {
         });
 
         globalState.onNewRequiredObservable.add(() => {
-            if (this._checkSafeMode("Are you sure you want to create a new playground?")) {
+            if (Utilities.CheckSafeMode("Are you sure you want to create a new playground?")) {
                 this._setNewContent();
                 this._isDirty = true;
             }
         });
 
         globalState.onClearRequiredObservable.add(() => {            
-            if (this._checkSafeMode("Are you sure you want to remove all your code?")) {
+            if (Utilities.CheckSafeMode("Are you sure you want to remove all your code?")) {
                 this._editor?.setValue("");
                 location.hash = "";
                 this._isDirty = true;
             }
         });
 
+        globalState.onNavigateRequiredObservable.add(position => {
+            this._editor?.revealPositionInCenter(position, monaco.editor.ScrollType.Smooth);
+            this._editor?.setPosition(position);
+        });
+
         globalState.onSavedObservable.add(() => {
             this._isDirty = false;
         })
@@ -76,6 +83,20 @@ export class MonacoManager {
             });
         });
 
+        globalState.onFontSizeChangedObservable.add(value => {
+            this._editor?.updateOptions({
+                fontSize: parseInt(Utilities.ReadStringFromStore("font-size", "14"))
+            });
+        });
+
+        globalState.onLanguageChangedObservable.add(() => {
+            this.setupMonacoAsync(this._hostElement);
+        });
+
+        globalState.onThemeChangedObservable.add(() => {
+            this._createEditor();
+        });
+
         // Register a global observable for inspector to request code changes
         let pgConnect = {
             onRequestCodeChangeObservable: new BABYLON.Observable()
@@ -92,6 +113,7 @@ export class MonacoManager {
     }
 
     private _setNewContent() {
+        this._createEditor();
         this._editor?.setValue(`// You have to create a function called createScene. This function must return a BABYLON.Scene object
     // You can reference the following variables: scene, canvas
     // You must at least define a camera
@@ -108,35 +130,17 @@ export class MonacoManager {
         this.globalState.onRunRequiredObservable.notifyObservers();
 
         location.hash = "";
-        if(location.pathname.indexOf('pg/') !== -1) {
+        if (location.pathname.indexOf('pg/') !== -1) {
             // reload to create a new pg if in full-path playground mode.
             window.location.pathname = '';
         }        
     }
 
-    private _checkSafeMode(message: string) {
-        if (Utilities.ReadBoolFromStore("safe-mode", false)) {
-            return window.confirm(message);
-        }
-
-        return true;
-    };
-
-    public async setupMonacoAsync(hostElement: HTMLDivElement) {
-        let response = await fetch("https://preview.babylonjs.com/babylon.d.ts");
-        if (!response.ok) {
-            return;
-        }
-
-        let libContent = await response.text();
-
-        response = await fetch("https://preview.babylonjs.com/gui/babylon.gui.d.ts");
-        if (!response.ok) {
-            return;
+    private _createEditor() {
+        if (this._editor) {
+            this._editor.dispose();
         }
 
-        libContent += await response.text();
-        
         var editorOptions: IStandaloneEditorConstructionOptions = {
             value: "",
             language: this.globalState.language === "JS" ? "javascript" : "typescript",
@@ -145,10 +149,11 @@ export class MonacoManager {
             automaticLayout: true,
             scrollBeyondLastLine: false,
             readOnly: false,
-            theme: "vs-dark",
+            theme: (Utilities.ReadStringFromStore("theme", "Light") === "Dark") ? "vs-dark" : "vs-light",
             contextmenu: false,
             folding: true,
             showFoldingControls: "always",
+            fontSize: parseInt(Utilities.ReadStringFromStore("font-size", "14")),
             renderIndentGuides: true,
             minimap: {
                 enabled: Utilities.ReadBoolFromStore("minimap", true)
@@ -156,7 +161,7 @@ export class MonacoManager {
         };      
 
         this._editor = monaco.editor.create(
-            hostElement,
+            this._hostElement,
             editorOptions as any
         );     
         
@@ -167,10 +172,32 @@ export class MonacoManager {
                 this._isDirty = true;
             }
         });
-        
-        if (!this.globalState.loadingCodeInProgress) {
-            this._setDefaultContent();
+
+        if (this.globalState.currentCode) {
+            this._editor!.setValue(this.globalState.currentCode);
+        }
+
+        this.globalState.getCompiledCode = () => this._getRunCode();
+    }
+
+    public async setupMonacoAsync(hostElement: HTMLDivElement) {
+        this._hostElement = hostElement;
+
+        let response = await fetch("https://preview.babylonjs.com/babylon.d.ts");
+        if (!response.ok) {
+            return;
         }
+
+        let libContent = await response.text();
+
+        response = await fetch("https://preview.babylonjs.com/gui/babylon.gui.d.ts");
+        if (!response.ok) {
+            return;
+        }
+
+        libContent += await response.text();
+        
+        this._createEditor();
         
         // Definition worker
         this._setupDefinitionWorker(libContent);
@@ -195,10 +222,16 @@ export class MonacoManager {
         }        
             
         this._hookMonacoCompletionProvider();
+        
+        if (!this.globalState.loadingCodeInProgress) {
+            this._setDefaultContent();
+        }
     }
 
     private _setDefaultContent() {
-        this._editor.setValue(`var createScene = function () {
+
+        if (this.globalState.language === "JS") {
+            this._editor.setValue(`var createScene = function () {
     // This creates a basic Babylon Scene object (non-mesh)
     var scene = new BABYLON.Scene(engine);
 
@@ -229,7 +262,44 @@ export class MonacoManager {
     return scene;
 
 };`
-        );
+                    );
+        } else {
+            this._editor.setValue(`class Playground { 
+    public static CreateScene(engine: BABYLON.Engine, canvas: HTMLCanvasElement): BABYLON.Scene {
+        // This creates a basic Babylon Scene object (non-mesh)
+        var scene = new BABYLON.Scene(engine);
+
+        // This creates and positions a free camera (non-mesh)
+        var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
+
+        // This targets the camera to scene origin
+        camera.setTarget(BABYLON.Vector3.Zero());
+
+        // This attaches the camera to the canvas
+        camera.attachControl(canvas, true);
+
+        // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
+        var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
+
+        // Default intensity is 1. Let's dim the light a small amount
+        light.intensity = 0.7;
+
+        // Our built-in 'sphere' shape. Params: name, subdivs, size, scene
+        var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene);
+
+        // Move the sphere upward 1/2 its height
+        sphere.position.y = 1;
+
+        // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
+        var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);
+
+        return scene;
+    }
+}`            
+                    );            
+        }
+
+
         this._isDirty = false;
             
         this.globalState.onRunRequiredObservable.notifyObservers();
@@ -460,4 +530,38 @@ export class MonacoManager {
         return tag &&
             tag.name == "deprecated";
     }
+
+    private async _getRunCode() {
+        if (this.globalState.language == "JS")
+            return this._editor.getValue();
+        else {
+            const model = this._editor.getModel()!;
+            const uri = model.uri;
+
+            const worker = await monaco.languages.typescript.getTypeScriptWorker();
+            const languageService = await worker(uri);
+
+            const uriStr = uri.toString();
+            const result = await languageService.getEmitOutput(uriStr);
+            const diagnostics = await Promise.all([languageService.getSyntacticDiagnostics(uriStr), languageService.getSemanticDiagnostics(uriStr)]);
+
+            diagnostics.forEach(function (diagset) {
+                if (diagset.length) {
+                    const diagnostic = diagset[0];
+                    const position = model.getPositionAt(diagnostic.start!);
+
+                    const err = new CompilationError();
+                    err.message = diagnostic.messageText as string;
+                    err.lineNumber = position.lineNumber;
+                    err.columnNumber = position.column;
+                    throw err;
+                }
+            });
+
+            const output = result.outputFiles[0].text;
+            const stub = "var createScene = function() { return Playground.CreateScene(engine, engine.getRenderingCanvas()); }";
+
+            return output + stub;
+        }
+    };
 }

+ 31 - 1
Playground/src/tools/utilities.ts

@@ -1,3 +1,5 @@
+import { GlobalState } from '../globalState';
+
 export class Utilities {
     public static FastEval(code: string) {
         var head = document.getElementsByTagName('head')[0];
@@ -23,6 +25,14 @@ export class Utilities {
         return query;
     }
 
+    public static ReadStringFromStore(key: string, defaultValue: string): string {
+        if (localStorage.getItem(key) === null) {
+            return defaultValue;
+        }
+
+        return localStorage.getItem(key)!;
+    }
+
     public static ReadBoolFromStore(key: string, defaultValue: boolean): boolean {
         if (localStorage.getItem(key) === null) {
             return defaultValue;
@@ -31,7 +41,27 @@ export class Utilities {
         return localStorage.getItem(key) === "true";
     }
 
-    public static StoreBoolFromStore(key: string, value: boolean): void {
+    public static StoreStringToStore(key: string, value: string): void {
+        localStorage.setItem(key, value);
+    }
+
+    public static StoreBoolToStore(key: string, value: boolean): void {
         localStorage.setItem(key, value ? "true" : "false");
     }
+
+    public static CheckSafeMode(message: string) {
+        if (Utilities.ReadBoolFromStore("safe-mode", false)) {
+            return window.confirm(message);
+        }
+
+        return true;
+    };
+
+    public static SwitchLanguage(language: string, globalState: GlobalState) {        
+        if (Utilities.CheckSafeMode("Are you sure you want to switch the language?")) {
+            Utilities.StoreStringToStore("language", language);
+            globalState.language = language;
+            globalState.onLanguageChangedObservable.notifyObservers();
+        }
+    }
 }

+ 2 - 2
Playground/old/ts.html

@@ -1,8 +1,8 @@
 <script>
-    localStorage.setItem("bjs-playground-scriptLanguage", "TS");
+    localStorage.setItem("language", "TS");
 
     var parseURL = window.location.href.split("ts.html");
     var hash = parseURL[parseURL.length - 1];
 
-    window.location = "./"+hash;
+    window.location = "./" + hash;
 </script>