1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

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
This commit is contained in:
Steve-Mcl 2021-05-03 16:19:01 +01:00
parent dedf5c52d9
commit ffbd140a97

View File

@ -99,6 +99,20 @@ RED.editor.codeEditor.monaco = (function() {
const modulesCache = {}; 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. * Helper function to load/reload types.
* @param {string} mod - type lib to load. Only known libs are currently supported. * @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 editorSettings = RED.editor.codeEditor.settings || {};
var loadedLibs = {JS:{}, TS:{}};//for tracking and later disposing of loaded type libs var loadedLibs = {JS:{}, TS:{}};//for tracking and later disposing of loaded type libs
var watchTimer, elVisible, elVisibleMem; var watchTimer;
var createThemeMenuOption = function (theme, keybinding) { var createThemeMenuOption = function (theme, keybinding) {
return { return {
// An unique identifier of the contributed action. // An unique identifier of the contributed action.
@ -708,7 +722,7 @@ RED.editor.codeEditor.monaco = (function() {
if (mode) { if (mode) {
mode = mode.replace("ace/mode/", ""); mode = mode.replace("ace/mode/", "");
} else { } else {
mode = "javascript"; mode = "text";
} }
switch (mode) { switch (mode) {
case "nrjavascript": case "nrjavascript":
@ -764,6 +778,7 @@ RED.editor.codeEditor.monaco = (function() {
if (editorOptions.lineNumbers === false) { editorOptions.lineNumbers = false; } if (editorOptions.lineNumbers === false) { editorOptions.lineNumbers = false; }
if (editorOptions.theme == null) { editorOptions.theme = monacoThemes[0]; } if (editorOptions.theme == null) { editorOptions.theme = monacoThemes[0]; }
if (editorOptions.mode == null) { editorOptions.mode = convertAceModeToMonacoLang(options.mode); } if (editorOptions.mode == null) { editorOptions.mode = convertAceModeToMonacoLang(options.mode); }
if (editorOptions.automaticLayout == null) { editorOptions.automaticLayout = true; }
if (options.foldStyle) { if (options.foldStyle) {
switch (options.foldStyle) { switch (options.foldStyle) {
@ -917,13 +932,8 @@ RED.editor.codeEditor.monaco = (function() {
ed.session = ed; ed.session = ed;
ed.renderer = {}; ed.renderer = {};
ed.setMode = function(mode, cb) { ed.setMode = function(mode, cb, resize) {
if (mode && typeof mode === "object") { if(resize==null) { resize = true; }
var options = mode;
mode = options.path;
} else {
mode = mode || "text";
}
mode = convertAceModeToMonacoLang(mode); mode = convertAceModeToMonacoLang(mode);
var oldModel = ed.getModel(); var oldModel = ed.getModel();
var oldValue = ed.getValue(); var oldValue = ed.getValue();
@ -935,13 +945,14 @@ RED.editor.codeEditor.monaco = (function() {
var oldSelections = ed.getSelections(); var oldSelections = ed.getSelections();
var oldPosition = ed.getPosition(); var oldPosition = ed.getPosition();
oldValue = oldModel.getValue() || ""; oldValue = oldModel.getValue() || "";
if(!oldModel.isDisposed()) { oldModel.dispose(); }
ed.setModel(null);
newModel = monaco.editor.createModel((oldValue || ""), mode); newModel = monaco.editor.createModel((oldValue || ""), mode);
ed.setModel(newModel); ed.setModel(newModel);
oldModel.dispose();
ed.setScrollTop(oldScrollTop, 1/* immediate */); ed.setScrollTop(oldScrollTop, 1/* immediate */);
ed.setScrollLeft(oldScrollLeft, 1/* immediate */); ed.setScrollLeft(oldScrollLeft, 1/* immediate */);
ed.setPosition(oldPosition); ed.setPosition(oldPosition);
ed.setSelections(oldSelections); ed.setSelections(oldSelections);
} else { } else {
newModel = monaco.editor.createModel((oldValue || ""), mode); newModel = monaco.editor.createModel((oldValue || ""), mode);
ed.setModel(newModel); ed.setModel(newModel);
@ -949,7 +960,9 @@ RED.editor.codeEditor.monaco = (function() {
if (cb && typeof cb == "function") { if (cb && typeof cb == "function") {
cb(); cb();
} }
this.resize();//cause a call to layout() if(resize) {
this.resize(); //cause a call to layout()
}
} }
ed.getRange = function getRange(){ ed.getRange = function getRange(){
@ -1032,7 +1045,9 @@ RED.editor.codeEditor.monaco = (function() {
} catch (error) { } } catch (error) { }
try { try {
var m = this.getModel(); var m = this.getModel();
m.dispose(); if(m && !m.isDisposed()) {
m.dispose();
}
this.setModel(null); this.setModel(null);
} catch (e) { } } catch (e) { }
@ -1182,41 +1197,19 @@ RED.editor.codeEditor.monaco = (function() {
//as models are signleton, consts and let are avialable to other javascript instances //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 //so when not focused, set editor mode to text temporarily to avoid multiple defs
ed.onDidBlurEditorWidget(function() { ed.onDidBlurEditorWidget(function() {
if(isVisible(el) == false) { if(isVisible(el) == false) {
if(ed._mode == "javascript") { onVisibilityChange(false);
ed.setMode('text');
ed._tempMode = "text";
}
} }
}); });
ed.onDidFocusEditorWidget(function() { ed.onDidFocusEditorWidget(function() {
if(ed._mode == "javascript" && ed._tempMode == "text") { onVisibilityChange(true, 10);
ed._tempMode = "";
setTimeout(function() {
ed.setMode('javascript');
}, 5);
}
}); });
watchVisibility(el, function(visible, element) { function visibilityWatcher(element, callback) {
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) {
try { try {
var options = { var options = {
root: $(element).closest("div.red-ui-tray-content")[0] || document, 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) { var observer = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) { entries.forEach(function(entry) {
callback(entry.intersectionRatio > 0, entry); callback(entry.intersectionRatio > 0, 5, entry);
}); });
}, options); }, options);
observer.observe(element); observer.observe(element);
} catch (e1) { } catch (e1) {
//browser not supporting IntersectionObserver? //browser not supporting IntersectionObserver? then fall back to polling!
//fall back to polling
try { try {
let elVisibleMem = isVisible(el)
watchTimer = setInterval(function() { watchTimer = setInterval(function() {
elVisible = isVisible(el); let elVisible = isVisible(el);
if(elVisible != elVisibleMem) { if(elVisible != elVisibleMem) {
callback(elVisible, element); callback(elVisible, 100, element);
} }
elVisible = elVisibleMem; elVisibleMem = elVisible;
}, 200); }, 100);
} catch (e2) { } } 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. //by default, set javascript editors to text mode.
//when elemt becomes visible, it will be (re) set to javascript 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 //this is to ensure multiple editors sharing the model dont presnet its
//consts & lets to each other //consts & lets to each other
if(ed._mode == "javascript") { if(ed._mode == "javascript") {
ed.setMode('text'); ed.setMode('text', undefined, false);
ed._tempMode = "text"; ed._tempMode = "text";
} }