From ffbd140a9710a954bc13fdf0f737365db37d2dcc Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 3 May 2021 16:19:01 +0100 Subject: [PATCH] bug fix orphaned models and graphical tray glitch - ensure models are disposed correctly - clean up any orphaned models left after editor:close - improve concurrent multiple instances javascript models - fix graphical glitch --- .../src/js/ui/editors/code-editors/monaco.js | 110 ++++++++++-------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index b1b19b248..4c8b944a8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -99,6 +99,20 @@ RED.editor.codeEditor.monaco = (function() { const modulesCache = {}; + RED.events.on("editor:close",function() { + //catch all - when editor is closed, ensure lod models that are not explicitly destroyed by a call to .destroy() are dumped + let models = monaco.editor.getModels(); + if(models && models.length) { + console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().") + for (let index = 0; index < models.length; index++) { + const model = models[index]; + if(!model.isDisposed()) { + model.dispose(); + } + } + } + }); + /** * Helper function to load/reload types. * @param {string} mod - type lib to load. Only known libs are currently supported. @@ -681,7 +695,7 @@ RED.editor.codeEditor.monaco = (function() { var editorSettings = RED.editor.codeEditor.settings || {}; var loadedLibs = {JS:{}, TS:{}};//for tracking and later disposing of loaded type libs - var watchTimer, elVisible, elVisibleMem; + var watchTimer; var createThemeMenuOption = function (theme, keybinding) { return { // An unique identifier of the contributed action. @@ -708,7 +722,7 @@ RED.editor.codeEditor.monaco = (function() { if (mode) { mode = mode.replace("ace/mode/", ""); } else { - mode = "javascript"; + mode = "text"; } switch (mode) { case "nrjavascript": @@ -764,6 +778,7 @@ RED.editor.codeEditor.monaco = (function() { if (editorOptions.lineNumbers === false) { editorOptions.lineNumbers = false; } if (editorOptions.theme == null) { editorOptions.theme = monacoThemes[0]; } if (editorOptions.mode == null) { editorOptions.mode = convertAceModeToMonacoLang(options.mode); } + if (editorOptions.automaticLayout == null) { editorOptions.automaticLayout = true; } if (options.foldStyle) { switch (options.foldStyle) { @@ -917,13 +932,8 @@ RED.editor.codeEditor.monaco = (function() { ed.session = ed; ed.renderer = {}; - ed.setMode = function(mode, cb) { - if (mode && typeof mode === "object") { - var options = mode; - mode = options.path; - } else { - mode = mode || "text"; - } + ed.setMode = function(mode, cb, resize) { + if(resize==null) { resize = true; } mode = convertAceModeToMonacoLang(mode); var oldModel = ed.getModel(); var oldValue = ed.getValue(); @@ -935,13 +945,14 @@ RED.editor.codeEditor.monaco = (function() { var oldSelections = ed.getSelections(); var oldPosition = ed.getPosition(); oldValue = oldModel.getValue() || ""; + if(!oldModel.isDisposed()) { oldModel.dispose(); } + ed.setModel(null); newModel = monaco.editor.createModel((oldValue || ""), mode); ed.setModel(newModel); - oldModel.dispose(); ed.setScrollTop(oldScrollTop, 1/* immediate */); ed.setScrollLeft(oldScrollLeft, 1/* immediate */); ed.setPosition(oldPosition); - ed.setSelections(oldSelections); + ed.setSelections(oldSelections); } else { newModel = monaco.editor.createModel((oldValue || ""), mode); ed.setModel(newModel); @@ -949,7 +960,9 @@ RED.editor.codeEditor.monaco = (function() { if (cb && typeof cb == "function") { cb(); } - this.resize();//cause a call to layout() + if(resize) { + this.resize(); //cause a call to layout() + } } ed.getRange = function getRange(){ @@ -1032,7 +1045,9 @@ RED.editor.codeEditor.monaco = (function() { } catch (error) { } try { var m = this.getModel(); - m.dispose(); + if(m && !m.isDisposed()) { + m.dispose(); + } this.setModel(null); } catch (e) { } @@ -1182,41 +1197,19 @@ RED.editor.codeEditor.monaco = (function() { //as models are signleton, consts and let are avialable to other javascript instances //so when not focused, set editor mode to text temporarily to avoid multiple defs + + ed.onDidBlurEditorWidget(function() { if(isVisible(el) == false) { - if(ed._mode == "javascript") { - ed.setMode('text'); - ed._tempMode = "text"; - } + onVisibilityChange(false); } }); ed.onDidFocusEditorWidget(function() { - if(ed._mode == "javascript" && ed._tempMode == "text") { - ed._tempMode = ""; - setTimeout(function() { - ed.setMode('javascript'); - }, 5); - } + onVisibilityChange(true, 10); }); - watchVisibility(el, function(visible, element) { - if(visible) { - if(ed._mode == "javascript" && ed._tempMode == "text") { - ed._tempMode = ""; - setTimeout(function() { - ed.setMode('javascript'); - }, 5); - } - } else { - if(ed._mode == "javascript") { - ed.setMode('text'); - ed._tempMode = "text"; - } - } - }); - - function watchVisibility(element, callback) { + function visibilityWatcher(element, callback) { try { var options = { root: $(element).closest("div.red-ui-tray-content")[0] || document, @@ -1225,32 +1218,51 @@ RED.editor.codeEditor.monaco = (function() { }; var observer = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { - callback(entry.intersectionRatio > 0, entry); + callback(entry.intersectionRatio > 0, 5, entry); }); }, options); observer.observe(element); } catch (e1) { - //browser not supporting IntersectionObserver? - //fall back to polling + //browser not supporting IntersectionObserver? then fall back to polling! try { + let elVisibleMem = isVisible(el) watchTimer = setInterval(function() { - elVisible = isVisible(el); + let elVisible = isVisible(el); if(elVisible != elVisibleMem) { - callback(elVisible, element); + callback(elVisible, 100, element); } - elVisible = elVisibleMem; - }, 200); + elVisibleMem = elVisible; + }, 100); } catch (e2) { } } - } + function onVisibilityChange(visible, delay, element) { + if(visible) { + if(ed._mode == "javascript" && ed._tempMode == "text") { + ed._tempMode = ""; + setTimeout(function() { + if(el.parentElement) { //ensure el is still in DOM + ed.setMode('javascript', undefined, false); + } + }, delay); + } + } else if(ed._mode == "javascript") { + if(el.parentElement) { //ensure el is still in DOM + ed.setMode('text', undefined, false); + ed._tempMode = "text"; + } + } + } + + visibilityWatcher(el, onVisibilityChange); + //by default, set javascript editors to text mode. //when elemt becomes visible, it will be (re) set to javascript mode //this is to ensure multiple editors sharing the model dont presnet its //consts & lets to each other if(ed._mode == "javascript") { - ed.setMode('text'); + ed.setMode('text', undefined, false); ed._tempMode = "text"; }