Browse Source

First monaco integration

David `Deltakosh` Catuhe 5 years ago
parent
commit
6930d120b2

+ 1 - 1
.vscode/launch.json

@@ -86,7 +86,7 @@
             "type": "edge",
             "version": "dev",
             "request": "launch",
-            "url": "http://localhost:1338/Playground/index-local.html",
+            "url": "http://localhost:1338/Playground/public/index-local.html",
             "webRoot": "${workspaceRoot}/",
             "sourceMaps": true,
             "preLaunchTask": "run",

+ 3 - 0
Playground/README-ES6.md

@@ -0,0 +1,3 @@
+# Babylon.js Playground
+
+An extension to easily create a full page playground (ala playground.babylonjs.com)

+ 16 - 0
Playground/README.md

@@ -0,0 +1,16 @@
+# Babylon.js Playground
+
+An extension to easily create a full page playground (ala playground.babylonjs.com)
+
+## Usage
+### Online method
+Call the method `Show` of the `BABYLON.Playground` class: 
+```
+BABYLON.Playground.Show({hostElement: document.getElementById("host")});
+```
+
+### Offline method
+If you don't have access to internet, the node editor should be imported manually in your HTML page :
+```
+<script src="babylon.playground.js" />
+``` 

+ 69 - 0
Playground/public/index-local.html

@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+
+    <head>
+        <title>Babylon.js Playground</title>
+        <meta charset='utf-8' />
+        <meta name="viewport" content="width=device-width, user-scalable=no">
+        <link rel="shortcut icon" href="https://www.babylonjs.com/favicon.ico">
+        <meta name="description" content="Babylon.js playground is a live editor for Babylon.js WebGL 3D scenes">
+        <meta name="keywords" content="Babylon.js,WebGL,3D">
+
+        <link rel="stylesheet"
+            href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" />
+        <link rel="stylesheet" href="/css/index.css" />
+        <link rel="stylesheet" href="/css/index_mobile.css" />
+
+        <!-- Pep -->
+        <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
+        <!--For canvas/code separator-->
+        <script src="libs/split.js"></script>
+
+        <!-- DatGUI -->
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
+        <!-- jszip -->
+        <script src="libs/jszip.min.js"></script>
+        <script src="libs/fileSaver.js"></script>
+
+        <!-- Dependencies -->
+        <script src="/dist/preview%20release/ammo.js"></script>
+        <script src="/dist/preview%20release/recast.js"></script>
+        <script src="/dist/preview%20release/cannon.js"></script>
+        <script src="/dist/preview%20release/Oimo.js"></script>
+        <script src="/dist/preview%20release/libktx.js"></script>
+        <script src="/dist/preview%20release/earcut.min.js"></script>
+        <!-- Monaco -->
+
+        <!-- Babylon.js -->
+        <script src="/Tools/DevLoader/BabylonLoader.js"></script>
+
+        <style>
+            html,
+            body {
+                width: 100%;
+                height: 100%;
+                padding: 0;
+                margin: 0;
+                overflow: hidden;
+            }
+    
+            #host-element {
+                width: 100%;
+                height: 100%;
+            }
+        </style>        
+    </head>
+
+    <body>        
+        <div id="host-element">
+        </div>
+        <script>
+            // Load the scripts + map file to allow vscode debug.
+            BABYLONDEVTOOLS.Loader            
+                .require("index.js")
+                .load(() => {
+                });
+        </script>
+    </body>
+
+</html>

+ 3 - 0
Playground/public/index.js

@@ -0,0 +1,3 @@
+var hostElement = document.getElementById("host-element");
+
+BABYLON.Playground.Show(hostElement);

+ 253 - 0
Playground/public/libs/fileSaver.js

@@ -0,0 +1,253 @@
+/*! FileSaver.js
+ *  A saveAs() FileSaver implementation.
+ *  2014-01-24
+ *
+ *  By Eli Grey, http://eligrey.com
+ *  License: X11/MIT
+ *    See LICENSE.md
+ */
+
+/*global self */
+/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+
+var saveAs = saveAs
+  // IE 10+ (native saveAs)
+  || (typeof navigator !== "undefined" &&
+      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
+  // Everyone else
+  || (function (view) {
+      "use strict";
+      // IE <10 is explicitly unsupported
+      if (typeof navigator !== "undefined" &&
+          /MSIE [1-9]\./.test(navigator.userAgent)) {
+          return;
+      }
+      var
+            doc = view.document
+            // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
+          , get_URL = function () {
+              return view.URL || view.webkitURL || view;
+          }
+          , URL = view.URL || view.webkitURL || view
+          , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+          , can_use_save_link = !view.externalHost && "download" in save_link
+          , click = function (node) {
+              var event = doc.createEvent("MouseEvents");
+              event.initMouseEvent(
+                  "click", true, false, view, 0, 0, 0, 0, 0
+                  , false, false, false, false, 0, null
+              );
+              node.dispatchEvent(event);
+          }
+          , webkit_req_fs = view.webkitRequestFileSystem
+          , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+          , throw_outside = function (ex) {
+              (view.setImmediate || view.setTimeout)(function () {
+                  throw ex;
+              }, 0);
+          }
+          , force_saveable_type = "application/octet-stream"
+          , fs_min_size = 0
+          , deletion_queue = []
+          , process_deletion_queue = function () {
+              var i = deletion_queue.length;
+              while (i--) {
+                  var file = deletion_queue[i];
+                  if (typeof file === "string") { // file is an object URL
+                      URL.revokeObjectURL(file);
+                  } else { // file is a File
+                      file.remove();
+                  }
+              }
+              deletion_queue.length = 0; // clear queue
+          }
+          , dispatch = function (filesaver, event_types, event) {
+              event_types = [].concat(event_types);
+              var i = event_types.length;
+              while (i--) {
+                  var listener = filesaver["on" + event_types[i]];
+                  if (typeof listener === "function") {
+                      try {
+                          listener.call(filesaver, event || filesaver);
+                      } catch (ex) {
+                          throw_outside(ex);
+                      }
+                  }
+              }
+          }
+          , FileSaver = function (blob, name) {
+              // First try a.download, then web filesystem, then object URLs
+              var
+                    filesaver = this
+                  , type = blob.type
+                  , blob_changed = false
+                  , object_url
+                  , target_view
+                  , get_object_url = function () {
+                      var object_url = get_URL().createObjectURL(blob);
+                      deletion_queue.push(object_url);
+                      return object_url;
+                  }
+                  , dispatch_all = function () {
+                      dispatch(filesaver, "writestart progress write writeend".split(" "));
+                  }
+                  // on any filesys errors revert to saving with object URLs
+                  , fs_error = function () {
+                      // don't create more object URLs than needed
+                      if (blob_changed || !object_url) {
+                          object_url = get_object_url(blob);
+                      }
+                      if (target_view) {
+                          target_view.location.href = object_url;
+                      } else {
+                          window.open(object_url, "_blank");
+                      }
+                      filesaver.readyState = filesaver.DONE;
+                      dispatch_all();
+                  }
+                  , abortable = function (func) {
+                      return function () {
+                          if (filesaver.readyState !== filesaver.DONE) {
+                              return func.apply(this, arguments);
+                          }
+                      };
+                  }
+                  , create_if_not_found = { create: true, exclusive: false }
+                  , slice
+              ;
+              filesaver.readyState = filesaver.INIT;
+              if (!name) {
+                  name = "download";
+              }
+              if (can_use_save_link) {
+                  object_url = get_object_url(blob);
+                  // FF for Android has a nasty garbage collection mechanism
+                  // that turns all objects that are not pure javascript into 'deadObject'
+                  // this means `doc` and `save_link` are unusable and need to be recreated
+                  // `view` is usable though:
+                  doc = view.document;
+                  save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
+                  save_link.href = object_url;
+                  save_link.download = name;
+                  var event = doc.createEvent("MouseEvents");
+                  event.initMouseEvent(
+                      "click", true, false, view, 0, 0, 0, 0, 0
+                      , false, false, false, false, 0, null
+                  );
+                  save_link.dispatchEvent(event);
+                  filesaver.readyState = filesaver.DONE;
+                  dispatch_all();
+                  return;
+              }
+              // Object and web filesystem URLs have a problem saving in Google Chrome when
+              // viewed in a tab, so I force save with application/octet-stream
+              // http://code.google.com/p/chromium/issues/detail?id=91158
+              if (view.chrome && type && type !== force_saveable_type) {
+                  slice = blob.slice || blob.webkitSlice;
+                  blob = slice.call(blob, 0, blob.size, force_saveable_type);
+                  blob_changed = true;
+              }
+              // Since I can't be sure that the guessed media type will trigger a download
+              // in WebKit, I append .download to the filename.
+              // https://bugs.webkit.org/show_bug.cgi?id=65440
+              if (webkit_req_fs && name !== "download") {
+                  name += ".download";
+              }
+              if (type === force_saveable_type || webkit_req_fs) {
+                  target_view = view;
+              }
+              if (!req_fs) {
+                  fs_error();
+                  return;
+              }
+              fs_min_size += blob.size;
+              req_fs(view.TEMPORARY, fs_min_size, abortable(function (fs) {
+                  fs.root.getDirectory("saved", create_if_not_found, abortable(function (dir) {
+                      var save = function () {
+                          dir.getFile(name, create_if_not_found, abortable(function (file) {
+                              file.createWriter(abortable(function (writer) {
+                                  writer.onwriteend = function (event) {
+                                      target_view.location.href = file.toURL();
+                                      deletion_queue.push(file);
+                                      filesaver.readyState = filesaver.DONE;
+                                      dispatch(filesaver, "writeend", event);
+                                  };
+                                  writer.onerror = function () {
+                                      var error = writer.error;
+                                      if (error.code !== error.ABORT_ERR) {
+                                          fs_error();
+                                      }
+                                  };
+                                  "writestart progress write abort".split(" ").forEach(function (event) {
+                                      writer["on" + event] = filesaver["on" + event];
+                                  });
+                                  writer.write(blob);
+                                  filesaver.abort = function () {
+                                      writer.abort();
+                                      filesaver.readyState = filesaver.DONE;
+                                  };
+                                  filesaver.readyState = filesaver.WRITING;
+                              }), fs_error);
+                          }), fs_error);
+                      };
+                      dir.getFile(name, { create: false }, abortable(function (file) {
+                          // delete file if it already exists
+                          file.remove();
+                          save();
+                      }), abortable(function (ex) {
+                          if (ex.code === ex.NOT_FOUND_ERR) {
+                              save();
+                          } else {
+                              fs_error();
+                          }
+                      }));
+                  }), fs_error);
+              }), fs_error);
+          }
+          , FS_proto = FileSaver.prototype
+          , saveAs = function (blob, name) {
+              return new FileSaver(blob, name);
+          }
+      ;
+      FS_proto.abort = function () {
+          var filesaver = this;
+          filesaver.readyState = filesaver.DONE;
+          dispatch(filesaver, "abort");
+      };
+      FS_proto.readyState = FS_proto.INIT = 0;
+      FS_proto.WRITING = 1;
+      FS_proto.DONE = 2;
+
+      FS_proto.error =
+      FS_proto.onwritestart =
+      FS_proto.onprogress =
+      FS_proto.onwrite =
+      FS_proto.onabort =
+      FS_proto.onerror =
+      FS_proto.onwriteend =
+          null;
+
+      view.addEventListener("unload", process_deletion_queue, false);
+      saveAs.unload = function () {
+          process_deletion_queue();
+          view.removeEventListener("unload", process_deletion_queue, false);
+      };
+      return saveAs;
+  }(
+	   typeof self !== "undefined" && self
+	|| typeof window !== "undefined" && window
+	|| this.content
+));
+// `self` is undefined in Firefox for Android content script context
+// while `this` is nsIContentFrameMessageManager
+// with an attribute `content` that corresponds to the window
+
+if (typeof module !== "undefined" && module !== null) {
+    module.exports = saveAs;
+} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
+    define([], function () {
+        return saveAs;
+    });
+}

File diff suppressed because it is too large
+ 14 - 0
Playground/public/libs/jszip.min.js


File diff suppressed because it is too large
+ 3 - 0
Playground/public/libs/split.js


+ 110 - 0
Playground/public/workers/definitionWorker.js

@@ -0,0 +1,110 @@
+// > This worker will analyze the syntaxtree and return an array of deprecated functions (but the goal is to do more in the future!)
+// We need to do this because:
+// - checking extended properties during completion is time consuming, so we need to prefilter potential candidates
+// - we don't want to maintain a static list of deprecated members or to instrument this work on the CI
+// - we have more plans involving syntaxtree analysis
+// > This worker was carefully crafted to work even if the processing is super fast or super long. 
+// In both cases the deprecation filter will start working after the worker is done.
+// We will also need this worker in the future to compute Intellicode scores for completion using dedicated attributes.
+
+// see monacoCreator.js/setupDefinitionWorker
+
+// monaco is using 'define' for module dependencies and service lookup.
+// hopefully typescript is self-contained
+var ts = null;
+var define = (id, dependencies, callback) => ts = callback();
+
+importScripts("../node_modules/monaco-editor/dev/vs/language/typescript/lib/typescriptServices.js");
+
+// store deprecated names
+var deprecatedCandidates = [];
+
+// optimize syntaxtree visitor, we don't care about non documented nodes
+function canHaveJsDoc(node) {
+    const kind = node.kind;
+    switch (kind) {
+        case ts.SyntaxKind.Parameter:
+        case ts.SyntaxKind.CallSignature:
+        case ts.SyntaxKind.ConstructSignature:
+        case ts.SyntaxKind.MethodSignature:
+        case ts.SyntaxKind.PropertySignature:
+        case ts.SyntaxKind.ArrowFunction:
+        case ts.SyntaxKind.ParenthesizedExpression:
+        case ts.SyntaxKind.SpreadAssignment:
+        case ts.SyntaxKind.ShorthandPropertyAssignment:
+        case ts.SyntaxKind.PropertyAssignment:
+        case ts.SyntaxKind.FunctionExpression:
+        case ts.SyntaxKind.FunctionDeclaration:
+        case ts.SyntaxKind.LabeledStatement:
+        case ts.SyntaxKind.ExpressionStatement:
+        case ts.SyntaxKind.VariableStatement:
+        case ts.SyntaxKind.Constructor:
+        case ts.SyntaxKind.MethodDeclaration:
+        case ts.SyntaxKind.PropertyDeclaration:
+        case ts.SyntaxKind.GetAccessor:
+        case ts.SyntaxKind.SetAccessor:
+        case ts.SyntaxKind.ClassDeclaration:
+        case ts.SyntaxKind.ClassExpression:
+        case ts.SyntaxKind.InterfaceDeclaration:
+        case ts.SyntaxKind.TypeAliasDeclaration:
+        case ts.SyntaxKind.EnumMember:
+        case ts.SyntaxKind.EnumDeclaration:
+        case ts.SyntaxKind.ModuleDeclaration:
+        case ts.SyntaxKind.ImportEqualsDeclaration:
+        case ts.SyntaxKind.IndexSignature:
+        case ts.SyntaxKind.FunctionType:
+        case ts.SyntaxKind.ConstructorType:
+        case ts.SyntaxKind.JSDocFunctionType:
+        case ts.SyntaxKind.EndOfFileToken:
+        case ts.SyntaxKind.ExportDeclaration:
+            return true;
+        default:
+            return false;
+    }
+}
+
+function onFindDeprecatedCandidate(node) {
+    const name = relatedName(node);
+    if (name)
+        deprecatedCandidates.push(name);
+}
+
+function relatedName(node) {
+    if (canHaveJsDoc(node) && node.name)
+        return node.name.escapedText;
+
+    if (node.parent)
+        return relatedName(parent);
+
+    return undefined;
+}
+
+function visit(node) {
+
+    if (node.jsDoc) {
+        for (const jsDocEntry of node.jsDoc) {
+            if (jsDocEntry.tags) {
+                for (const tag of jsDocEntry.tags) {
+                    if (tag.tagName && tag.tagName.escapedText == 'deprecated')
+                        onFindDeprecatedCandidate(node);
+                }
+            }
+        }
+    }
+
+    ts.forEachChild(node, visit);
+}
+
+function processDefinition(code) {
+    if (deprecatedCandidates.length == 0) {
+        const sourceFile = ts.createSourceFile('babylon.js', code, ts.ScriptTarget.ESNext, true);
+        ts.forEachChild(sourceFile, visit);
+    }
+
+    self.postMessage({ result: deprecatedCandidates });
+}
+
+self.addEventListener('message', event => {
+    const { code } = event.data;
+    processDefinition(code);
+});

+ 262 - 0
Playground/src/components/monacoComponent.tsx

@@ -0,0 +1,262 @@
+import * as React from "react";
+import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
+
+require("../scss/monaco.scss");
+
+interface IMonacoComponentProps {
+    language: "JS" | "TS";
+}
+
+export class MonacoComponent extends React.Component<IMonacoComponentProps> {
+    private _hostReference: React.RefObject<HTMLDivElement>;
+    private _editor: monaco.editor.IStandaloneCodeEditor;
+    private _definitionWorker: Worker;
+    private _deprecatedCandidates: string[];
+    private _templates: string[];
+    
+    public constructor(props: IMonacoComponentProps) {
+        super(props);
+
+        this._hostReference = React.createRef();
+    }
+
+    async setupMonaco() {
+        let hostElement = this._hostReference.current!;  
+        var editorOptions: monaco.editor.IEditorConstructionOptions = {
+            value: "",
+            language: this.props.language == "JS" ? "javascript" : "typescript",
+            lineNumbers: "on",
+            roundedSelection: true,
+            automaticLayout: true,
+            scrollBeyondLastLine: false,
+            readOnly: false,
+            theme: "vs",
+            contextmenu: false,
+            folding: true,
+            showFoldingControls: "always",
+            renderIndentGuides: true,
+            minimap: {
+                enabled: true
+            }
+        };      
+
+        this._editor = monaco.editor.create(
+            hostElement,
+            editorOptions
+        );
+
+        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.setupDefinitionWorker(libContent);
+
+        // Load code templates
+        response = await fetch("/templates.json");
+        if (response.ok) {
+            this._templates = await response.json();
+        }
+
+        // Setup the Monaco compilation pipeline, so we can reuse it directly for our scrpting needs
+        this.setupMonacoCompilationPipeline(libContent);
+
+        // This is used for a vscode-like color preview for ColorX types
+        this.setupMonacoColorProvider();
+    }
+
+      // Provide an adornment for BABYLON.ColorX types: color preview
+      setupMonacoColorProvider() {
+        monaco.languages.registerColorProvider(this.props.language == "JS" ? "javascript" : "typescript", {
+            provideColorPresentations: (model, colorInfo) => {
+                const color = colorInfo.color;
+
+                const precision = 100.0;
+                const converter = (n: number) => Math.round(n * precision) / precision;
+
+                let label;
+                if (color.alpha === undefined || color.alpha === 1.0) {
+                    label = `(${converter(color.red)}, ${converter(color.green)}, ${converter(color.blue)})`;
+                } else {
+                    label = `(${converter(color.red)}, ${converter(color.green)}, ${converter(color.blue)}, ${converter(color.alpha)})`;
+                }
+
+                return [{
+                    label: label
+                }];
+            },
+
+            provideDocumentColors: (model) => {
+                const digitGroup = "\\s*(\\d*(?:\\.\\d+)?)\\s*";
+                // we add \n{0} to workaround a Monaco bug, when setting regex options on their side
+                const regex = `BABYLON\\.Color(?:3|4)\\s*\\(${digitGroup},${digitGroup},${digitGroup}(?:,${digitGroup})?\\)\\n{0}`;
+                const matches = model.findMatches(regex, false, true, true, null, true);
+
+                const converter = (g: string) => g === undefined ? undefined : Number(g);
+
+                return matches.map(match => ({
+                    color: {
+                        red: converter(match.matches![1])!,
+                        green: converter(match.matches![2])!,
+                        blue: converter(match.matches![3])!,
+                        alpha: converter(match.matches![4])!
+                    },
+                    range: {
+                        startLineNumber: match.range.startLineNumber,
+                        startColumn: match.range.startColumn + match.matches![0].indexOf("("),
+                        endLineNumber: match.range.startLineNumber,
+                        endColumn: match.range.endColumn
+                    }
+                }));
+            }
+        });
+    }
+
+    // Setup both JS and TS compilation pipelines to work with our scripts. 
+    setupMonacoCompilationPipeline(libContent: string) {
+        const typescript = monaco.languages.typescript;
+
+        if (this.props.language == "JS") {
+            typescript.javascriptDefaults.setCompilerOptions({
+                noLib: false,
+                allowNonTsExtensions: true // required to prevent Uncaught Error: Could not find file: 'inmemory://model/1'.
+            });
+
+            typescript.javascriptDefaults.addExtraLib(libContent, 'babylon.d.ts');
+        } else {
+            typescript.typescriptDefaults.setCompilerOptions({
+                module: typescript.ModuleKind.AMD,
+                target: typescript.ScriptTarget.ESNext,
+                noLib: false,
+                strict: false,
+                alwaysStrict: false,
+                strictFunctionTypes: false,
+                suppressExcessPropertyErrors: false,
+                suppressImplicitAnyIndexErrors: true,
+                noResolve: true,
+                suppressOutputPathCheck: true,
+
+                allowNonTsExtensions: true // required to prevent Uncaught Error: Could not find file: 'inmemory://model/1'.
+            });
+            typescript.typescriptDefaults.addExtraLib(libContent, 'babylon.d.ts');
+        }
+    }
+
+    setupDefinitionWorker(libContent: string) {
+        this._definitionWorker = new Worker('workers/definitionWorker.js');
+        this._definitionWorker.addEventListener('message', ({
+            data
+        }) => {
+            this._deprecatedCandidates = data.result;
+            this.analyzeCode();
+        });
+        this._definitionWorker.postMessage({
+            code: libContent
+        });
+    }
+
+    // This will make sure that all members marked with a deprecated jsdoc attribute will be marked as such in Monaco UI
+    // We use a prefiltered list of deprecated candidates, because the effective call to getCompletionEntryDetails is slow.
+    // @see setupDefinitionWorker
+    async analyzeCode() {
+        // if the definition worker is very fast, this can be called out of context. @see setupDefinitionWorker
+        if (!this._editor)
+            return;
+
+        const model = this._editor.getModel();
+        if (!model)
+            return;
+
+        const uri = model.uri;
+
+        let worker = null;
+        if (this.props.language == "JS")
+            worker = await monaco.languages.typescript.getJavaScriptWorker();
+        else
+            worker = await monaco.languages.typescript.getTypeScriptWorker();
+
+        const languageService = await worker(uri);
+        const source = '[deprecated members]';
+
+        monaco.editor.setModelMarkers(model, source, []);
+        const markers: {
+            startLineNumber: number,
+            endLineNumber: number,
+            startColumn: number,
+            endColumn: number,
+            message: string,
+            severity: monaco.MarkerSeverity.Warning,
+            source: string,
+        }[] = [];
+
+        for (const candidate of this._deprecatedCandidates) {
+            const matches = model.findMatches(candidate, false, false, true, null, false);
+            for (const match of matches) {
+                const position = {
+                    lineNumber: match.range.startLineNumber,
+                    column: match.range.startColumn
+                };
+                const wordInfo = model.getWordAtPosition(position);
+                const offset = model.getOffsetAt(position);
+
+                if (!wordInfo) {
+                    continue;
+                }
+
+                // continue if we already found an issue here
+                if (markers.find(m => m.startLineNumber == position.lineNumber && m.startColumn == position.column))
+                    continue;
+
+                // the following is time consuming on all suggestions, that's why we precompute deprecated candidate names in the definition worker to filter calls
+                // @see setupDefinitionWorker
+                const details = await languageService.getCompletionEntryDetails(uri.toString(), offset, wordInfo.word);
+                if (this.isDeprecatedEntry(details)) {
+                    const deprecatedInfo = details.tags.find(this.isDeprecatedTag);
+                    markers.push({
+                        startLineNumber: match.range.startLineNumber,
+                        endLineNumber: match.range.endLineNumber,
+                        startColumn: wordInfo.startColumn,
+                        endColumn: wordInfo.endColumn,
+                        message: deprecatedInfo.text,
+                        severity: monaco.MarkerSeverity.Warning,
+                        source: source,
+                    });
+                }
+            }
+        }
+
+        monaco.editor.setModelMarkers(model, source, markers);
+    }
+
+    isDeprecatedEntry(details: any) {
+        return details &&
+            details.tags &&
+            details.tags.find(this.isDeprecatedTag);
+    }
+
+    isDeprecatedTag(tag: any) {
+        return tag &&
+            tag.name == "deprecated";
+    }
+
+    componentDidMount() {
+       this.setupMonaco();
+    }
+
+    public render() {
+
+        return (
+            <div id="monacoHost" ref={this._hostReference}>               
+            </div>   
+        )
+    }
+}

+ 2 - 0
Playground/src/globalState.ts

@@ -0,0 +1,2 @@
+export class GlobalState {
+}

+ 1 - 0
Playground/src/index.ts

@@ -0,0 +1 @@
+export * from "./playground";

+ 9 - 0
Playground/src/legacy/legacy.ts

@@ -0,0 +1,9 @@
+import { Playground } from "../index";
+
+var globalObject = (typeof global !== 'undefined') ? global : ((typeof window !== 'undefined') ? window : undefined);
+if (typeof globalObject !== "undefined") {
+    (<any>globalObject).BABYLON = (<any>globalObject).BABYLON || {};
+    (<any>globalObject).BABYLON.Playground = Playground;
+}
+
+export * from "../index";

+ 35 - 0
Playground/src/playground.tsx

@@ -0,0 +1,35 @@
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import { MonacoComponent } from './components/monacoComponent';
+//import { GlobalState } from './globalState';
+
+require("./scss/main.scss");
+
+interface IPlaygroundProps {
+}
+
+export class Playground extends React.Component<IPlaygroundProps, {errorMessage: string}> {
+    //private _globalState: GlobalState;
+    
+    public constructor(props: IPlaygroundProps) {
+        super(props);
+       // this._globalState = new GlobalState();
+
+       this.state = {errorMessage: ""};
+    }
+
+    public render() {
+
+        return (
+            <div id="root">  
+                <MonacoComponent language="JS"/>    
+            </div>   
+        )
+    }
+
+    public static Show(hostElement: HTMLElement) {
+        const playground = React.createElement(Playground, {});
+        
+        ReactDOM.render(playground, hostElement);
+    }
+}

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

@@ -0,0 +1,21 @@
+html {
+    --background: #2A2342;
+    --footer-background: #201936;
+    --footer-height: 70px;
+    --button-hover-color: #BB464B;
+    --button-hover-hover: #e0684b;
+    --button-hover-background:  #162D3A;
+    --font-size: 20px;
+}
+
+html, body, #root {
+    width: 100%;
+    height: 100%;
+    padding: 0;
+    margin: 0;
+    overflow: hidden;
+    font-size: var(--font-size);
+    background: var(--background);
+    font-family: "acumin-pro-condensed";
+    font-weight: normal;    
+}

+ 6 - 0
Playground/src/scss/monaco.scss

@@ -0,0 +1,6 @@
+#monacoHost {
+    width: 100%;
+    height: 100%;
+    padding: 0;
+    margin: 0;
+}

+ 28 - 0
Playground/tsconfig.json

@@ -0,0 +1,28 @@
+{
+    "extends": "../tsconfigRules",
+    "compilerOptions": {
+        "jsx": "react",
+        "baseUrl": "./src/",
+        "rootDir": "./src/",
+        "paths": {
+            "babylonjs-gui/*": [
+                "../../dist/preview release/gui/babylon.gui.module.d.ts"
+            ],
+            "babylonjs-gltf2interface": [
+                "../../dist/preview release/glTF2Interface/babylon.glTF2Interface.d.ts"
+            ],
+            "babylonjs-loaders/*": [
+                "../../dist/preview release/loaders/babylonjs.loaders.module.d.ts"
+            ],
+            "babylonjs-materials/*": [
+                "../../dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts"
+            ],
+            "babylonjs-serializers/*": [
+                "../../dist/preview release/serializers/babylonjs.serializers.module.d.ts"
+            ],
+            "babylonjs/*": [
+                "../../dist/preview release/babylon.module.d.ts"
+            ]
+        }
+    }
+}

+ 46 - 0
Playground/webpack.config.js

@@ -0,0 +1,46 @@
+const path = require("path");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const babylonWebpackConfig = require('../Tools/WebpackPlugins/babylonWebpackConfig');
+
+var config = babylonWebpackConfig({
+    module: "playground",
+    resolve: {
+        extensions: [".js", '.ts', ".tsx"],
+    },
+    moduleRules: [
+        {
+            test: /\.scss$/,
+            use: [
+                // fallback to style-loader in development
+                process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader,
+                "css-loader",
+                "sass-loader"
+            ]
+        }, 
+        {
+            test: /\.css$/,
+            use: ['style-loader', 'css-loader']
+        },
+        {
+            test: /\.svg$/,
+            use: [
+              {
+                loader: 'svg-url-loader',
+                options: {
+                  limit: 10000,
+                },
+              },
+            ],
+          }
+    ],
+    plugins: [
+        new MiniCssExtractPlugin({
+            // Options similar to the same options in webpackOptions.output
+            // both options are optional
+            filename: "[name].css",
+            chunkFilename: "[id].css"
+        })
+    ]
+});
+
+module.exports = config;

+ 52 - 2
Tools/Config/config.json

@@ -48,7 +48,8 @@
         "gui",
         "inspector",
         "nodeEditor",
-        "sandbox"
+        "sandbox",
+        "playground"
     ],
     "es6modules": [
         "core",
@@ -61,7 +62,8 @@
         "inspector",
         "viewer",
         "nodeEditor",
-        "sandbox"
+        "sandbox",
+        "playground"
     ],
     "lintModules": [
         "core",
@@ -686,6 +688,54 @@
             }
         }
     },
+    "playground": {
+        "libraries": [
+            {
+                "output": "babylon.playground.js",
+                "entry": "./legacy/legacy.ts"
+            }
+        ],
+        "build": {            
+            "ignoreInWorkerMode": true,
+            "ignoreInTestMode": true,
+            "mainFolder": "./playground/",
+            "uncheckedLintImports": [
+                "react",
+                "react-dom"
+            ],
+            "umd": {
+                "packageName": "babylonjs-playground",
+                "webpackRoot": "PLAYGROUND",
+                "processDeclaration": {
+                    "filename": "babylon.playground.module.d.ts",
+                    "moduleName": "PLAYGROUND",
+                    "importsToRemove": [],
+                    "classMap": {
+                        "babylonjs": "BABYLON",
+                        "react": "React",
+                        "@babylonjs/core": "BABYLON",
+                        "@fortawesome": false
+                    }
+                }
+            },
+            "es6": {
+                "webpackBuild": true,
+                "buildDependencies": [
+                    "Tools/**/*"
+                ],
+                "packageName": "@babylonjs/playground",
+                "readme": "dist/preview release/playground/readme-es6.md",
+                "packagesFiles": [
+                    "babylon.playground.max.js",
+                    "babylon.playground.max.js.map",
+                    "babylon.playground.module.d.ts",
+                    "readme.md"
+                ],
+                "typings": "babylon.playground.module.d.ts",
+                "index": "babylon.playground.max.js"
+            }
+        }
+    },
     "viewer": {
         "libraries": [
             {

+ 17 - 0
dist/preview release/playground/babylon.playground.d.ts

@@ -0,0 +1,17 @@
+/// <reference types="react" />
+declare module PLAYGROUND {
+    export class GlobalState {
+    }
+}
+declare module PLAYGROUND {
+    interface IPlaygroundProps {
+    }
+    export class Playground extends React.Component<IPlaygroundProps, {
+        isFooterVisible: boolean;
+        errorMessage: string;
+    }> {
+        constructor(props: IPlaygroundProps);
+        render(): JSX.Element;
+        static Show(hostElement: HTMLElement): void;
+    }
+}

File diff suppressed because it is too large
+ 45 - 0
dist/preview release/playground/babylon.playground.js


File diff suppressed because it is too large
+ 29424 - 0
dist/preview release/playground/babylon.playground.max.js


File diff suppressed because it is too large
+ 1 - 0
dist/preview release/playground/babylon.playground.max.js.map


+ 44 - 0
dist/preview release/playground/babylon.playground.module.d.ts

@@ -0,0 +1,44 @@
+/// <reference types="react" />
+declare module "babylonjs-playground/globalState" {
+    export class GlobalState {
+    }
+}
+declare module "babylonjs-playground/playground" {
+    import * as React from "react";
+    interface IPlaygroundProps {
+    }
+    export class Playground extends React.Component<IPlaygroundProps, {
+        isFooterVisible: boolean;
+        errorMessage: string;
+    }> {
+        constructor(props: IPlaygroundProps);
+        render(): JSX.Element;
+        static Show(hostElement: HTMLElement): void;
+    }
+}
+declare module "babylonjs-playground/index" {
+    export * from "babylonjs-playground/playground";
+}
+declare module "babylonjs-playground/legacy/legacy" {
+    export * from "babylonjs-playground/index";
+}
+declare module "babylonjs-playground" {
+    export * from "babylonjs-playground/legacy/legacy";
+}
+/// <reference types="react" />
+declare module PLAYGROUND {
+    export class GlobalState {
+    }
+}
+declare module PLAYGROUND {
+    interface IPlaygroundProps {
+    }
+    export class Playground extends React.Component<IPlaygroundProps, {
+        isFooterVisible: boolean;
+        errorMessage: string;
+    }> {
+        constructor(props: IPlaygroundProps);
+        render(): JSX.Element;
+        static Show(hostElement: HTMLElement): void;
+    }
+}

+ 27 - 0
dist/preview release/playground/package.json

@@ -0,0 +1,27 @@
+{
+    "author": {
+        "name": "David CATUHE"
+    },
+    "name": "babylonjs-playground",
+    "description": "The Babylon.js playground",
+    "version": "4.2.0-alpha.23",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/BabylonJS/Babylon.js.git"
+    },
+    "license": "Apache-2.0",
+    "dependencies": {
+        "babylonjs": "4.2.0-alpha.23"
+    },
+    "files": [
+        "babylon.playground.max.js.map",
+        "babylon.playground.max.js",
+        "babylon.playground.js",
+        "babylon.playground.module.d.ts",
+        "readme.md",
+        "package.json"
+    ],
+    "engines": {
+        "node": "*"
+    }
+}

+ 3 - 0
dist/preview release/playground/readme-es6.md

@@ -0,0 +1,3 @@
+# Babylon.js Playground
+
+An extension to easily create a full page playground (ala playground.babylonjs.com)

+ 16 - 0
dist/preview release/playground/readme.md

@@ -0,0 +1,16 @@
+# Babylon.js Playground
+
+An extension to easily create a full page playground (ala playground.babylonjs.com)
+
+## Usage
+### Online method
+Call the method `Show` of the `BABYLON.Playground` class: 
+```
+BABYLON.Playground.Show({hostElement: document.getElementById("host")});
+```
+
+### Offline method
+If you don't have access to internet, the node editor should be imported manually in your HTML page :
+```
+<script src="babylon.playground.js" />
+```