feat(ui/design): Monaco editor for script code fields
Vendors Monaco 0.55.1 min/vs/ (~15 MB) at wwwroot/lib/monaco/vs/. No CDN dependency; works on air-gapped deployments. Loaded lazily on first script-edit via the AMD loader. wwwroot/js/monaco-init.js exposes window.MonacoBlazor with createEditor / setValue / getValue / setMarkers / dispose. Handles loader bootstrap, DotNet round-trip on content change, and marker sets for later diagnostic wiring. Components/Shared/MonacoEditor.razor is a Blazor wrapper with Value / ValueChanged / Language / Height / ReadOnly parameters and IAsyncDisposable teardown. Bidirectional binding tracks _lastSentValue to avoid push/pull loops. Replaces the plain textareas in SharedScriptForm, TemplateEdit's Add-Script form, and ApiMethodForm. Default height 320px ≈ the previous rows=10. Build / tests / dialog flow unaffected. Wave 1 of three. Roslyn-backed completions and SCADA-specific extensions follow in subsequent commits.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
// Blazor bridge for Monaco editor.
|
||||
// Exposes window.MonacoBlazor with createEditor / setValue / getValue / dispose / setMarkers.
|
||||
// Lazy-loads Monaco's AMD bundle the first time createEditor is called.
|
||||
|
||||
(function () {
|
||||
const VS_BASE = "/_content/ScadaLink.CentralUI/lib/monaco/vs";
|
||||
|
||||
const editors = {};
|
||||
let readyPromise = null;
|
||||
|
||||
function ensureLoaded() {
|
||||
if (readyPromise) return readyPromise;
|
||||
readyPromise = new Promise(function (resolve, reject) {
|
||||
const loader = document.createElement("script");
|
||||
loader.src = VS_BASE + "/loader.js";
|
||||
loader.onload = function () {
|
||||
// eslint-disable-next-line no-undef
|
||||
require.config({ paths: { vs: VS_BASE } });
|
||||
// eslint-disable-next-line no-undef
|
||||
require(["vs/editor/editor.main"], function () {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
loader.onerror = function (e) { reject(e); };
|
||||
document.head.appendChild(loader);
|
||||
});
|
||||
return readyPromise;
|
||||
}
|
||||
|
||||
async function createEditor(id, host, options, dotNetRef) {
|
||||
await ensureLoaded();
|
||||
if (!host) return;
|
||||
const editor = monaco.editor.create(host, {
|
||||
value: options.value || "",
|
||||
language: options.language || "csharp",
|
||||
theme: "vs",
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
fontSize: 13,
|
||||
lineNumbers: "on",
|
||||
renderLineHighlight: "line",
|
||||
readOnly: !!options.readOnly,
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
wordWrap: "off",
|
||||
fixedOverflowWidgets: true
|
||||
});
|
||||
editor.onDidChangeModelContent(function () {
|
||||
const value = editor.getValue();
|
||||
dotNetRef.invokeMethodAsync("OnValueChanged", value).catch(function () {});
|
||||
});
|
||||
editors[id] = { editor: editor, dotNetRef: dotNetRef };
|
||||
}
|
||||
|
||||
function setValue(id, value) {
|
||||
const entry = editors[id];
|
||||
if (!entry) return;
|
||||
if (entry.editor.getValue() !== value) {
|
||||
entry.editor.setValue(value || "");
|
||||
}
|
||||
}
|
||||
|
||||
function getValue(id) {
|
||||
const entry = editors[id];
|
||||
return entry ? entry.editor.getValue() : null;
|
||||
}
|
||||
|
||||
function setMarkers(id, markers) {
|
||||
const entry = editors[id];
|
||||
if (!entry) return;
|
||||
const model = entry.editor.getModel();
|
||||
if (!model) return;
|
||||
monaco.editor.setModelMarkers(model, "scadalink", markers || []);
|
||||
}
|
||||
|
||||
function dispose(id) {
|
||||
const entry = editors[id];
|
||||
if (!entry) return;
|
||||
try { entry.editor.dispose(); } catch (e) {}
|
||||
delete editors[id];
|
||||
}
|
||||
|
||||
window.MonacoBlazor = {
|
||||
createEditor: createEditor,
|
||||
setValue: setValue,
|
||||
getValue: getValue,
|
||||
setMarkers: setMarkers,
|
||||
dispose: dispose
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user