Files
mcDandy-more_math/web/script_text_input.js
T
2026-02-20 23:27:04 +01:00

257 lines
7.7 KiB
JavaScript

import { app } from "/scripts/app.js";
const NODE_IDS = new Set(["mrmth_ScriptInput"]);
const NODE_NAMES = new Set(["Audio -> Spectrogram", "ScriptTextInput", "STEW"]);
const STYLE_ID = "mrmth-script-line-numbers";
const ACE_SCRIPT_SRC = "/extensions/more_math/ace/ace.js";
let aceLoader = null;
function hasScriptInput(nodeData) {
const input = nodeData?.input || {};
const required = input.required || {};
const optional = input.optional || {};
return Object.prototype.hasOwnProperty.call(required, "script") || Object.prototype.hasOwnProperty.call(optional, "script");
}
function isTargetNode(nodeData) {
if (!nodeData) {
return false;
}
if (NODE_IDS.has(nodeData.node_id)) {
return true;
}
if (NODE_IDS.has(nodeData.name)) {
return true;
}
if (NODE_NAMES.has(nodeData.name)) {
return true;
}
if (NODE_NAMES.has(nodeData.display_name)) {
return true;
}
return hasScriptInput(nodeData);
}
function ensureStyles() {
if (document.getElementById(STYLE_ID)) {
return;
}
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = `
.mrmth-line-editor {
display: flex;
align-items: stretch;
width: 100%;
gap: 0;
}
.mrmth-line-numbers {
padding: 6px 8px;
text-align: right;
user-select: none;
color: #9aa0a6;
background: rgba(0, 0, 0, 0.2);
border-right: 1px solid rgba(255, 255, 255, 0.1);
font-family: monospace;
font-size: 12px;
line-height: 1.4;
white-space: pre;
overflow: hidden;
box-sizing: border-box;
}
.mrmth-line-numbers-content {
white-space: pre;
}
.mrmth-line-input {
flex: 1;
font-family: monospace;
line-height: 1.4;
}
.mrmth-ace-editor {
width: 100%;
min-height: 120px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 4px;
}
`;
document.head.appendChild(style);
}
function loadAce() {
if (window.ace) {
return Promise.resolve(window.ace);
}
if (aceLoader) {
return aceLoader;
}
aceLoader = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = ACE_SCRIPT_SRC;
script.async = true;
script.onload = () => resolve(window.ace);
script.onerror = () => reject(new Error("Failed to load Ace editor"));
document.head.appendChild(script);
});
return aceLoader;
}
function attachAceEditor(widget) {
const inputEl = widget?.inputEl;
if (!inputEl) {
return false;
}
if (widget.mrmthAceEditor) {
if (widget.value !== undefined && widget.mrmthAceEditor.getValue() !== widget.value) {
widget.mrmthAceEditor.setValue(String(widget.value), -1);
}
return true;
}
const parent = inputEl.parentElement;
if (!parent) {
return false;
}
ensureStyles();
const inputStyle = getComputedStyle(inputEl);
const parentStyle = getComputedStyle(parent);
inputEl.style.display = "none";
const editorEl = document.createElement("div");
editorEl.className = "mrmth-ace-editor";
editorEl.style.height = `${inputEl.offsetHeight}px`;
editorEl.style.background = inputStyle.backgroundColor;
editorEl.style.color = inputStyle.color;
editorEl.style.borderColor = inputStyle.borderColor || parentStyle.borderColor;
editorEl.style.borderRadius = inputStyle.borderRadius || parentStyle.borderRadius;
editorEl.style.fontFamily = inputStyle.fontFamily;
editorEl.style.fontSize = inputStyle.fontSize;
editorEl.style.lineHeight = inputStyle.lineHeight;
editorEl.style.letterSpacing = inputStyle.letterSpacing;
parent.appendChild(editorEl);
const aceEditor = window.ace.edit(editorEl, {
showLineNumbers: true,
showGutter: true,
wrap: true,
useWorker: false,
});
if (inputStyle.fontFamily) {
aceEditor.setOption("fontFamily", inputStyle.fontFamily);
}
const fontSize = parseFloat(inputStyle.fontSize);
const lineHeightPx = parseFloat(inputStyle.lineHeight);
if (Number.isFinite(fontSize)) {
aceEditor.setOption("fontSize", fontSize);
}
if (Number.isFinite(lineHeightPx) && Number.isFinite(fontSize) && fontSize > 0) {
aceEditor.setOption("lineHeight", lineHeightPx / fontSize);
}
const paddingLeft = parseFloat(inputStyle.paddingLeft);
const paddingRight = parseFloat(inputStyle.paddingRight);
if (Number.isFinite(paddingLeft)) {
aceEditor.renderer.setPadding(paddingLeft, Number.isFinite(paddingRight) ? paddingRight : paddingLeft);
}
const themeBackground = inputStyle.backgroundColor || parentStyle.backgroundColor;
const themeForeground = inputStyle.color || parentStyle.color;
const gutterBackground = parentStyle.backgroundColor || themeBackground;
const gutterForeground = parentStyle.color || themeForeground;
aceEditor.renderer.setStyle(`
.ace_editor {
background: ${themeBackground} !important;
color: ${themeForeground} !important;
}
.ace_scroller {
background: ${themeBackground} !important;
}
.ace_gutter {
background: ${gutterBackground} !important;
color: ${gutterForeground} !important;
}
.ace_gutter-layer {
background: ${gutterBackground} !important;
}
.ace_gutter-cell {
background: ${gutterBackground} !important;
color: ${gutterForeground} !important;
}
.ace_gutter-active-line {
background: ${themeBackground} !important;
}
.ace_cursor {
color: ${themeForeground} !important;
}
`);
aceEditor.renderer.updateFontSize();
aceEditor.renderer.onResize(true);
aceEditor.setValue(String(widget.value ?? inputEl.value ?? ""), -1);
aceEditor.on("change", () => {
const value = aceEditor.getValue();
widget.value = value;
inputEl.value = value;
});
if (window.ResizeObserver) {
const resizeObserver = new ResizeObserver(() => {
editorEl.style.height = `${inputEl.offsetHeight}px`;
aceEditor.resize();
});
resizeObserver.observe(inputEl);
}
widget.mrmthAceEditor = aceEditor;
return true;
}
function attachLineNumbersWithRetry(node) {
const widget = node.widgets?.find((w) => w.name === "script");
if (!widget) {
return;
}
loadAce()
.then(() => attachAceEditor(widget))
.catch(() => attachLineNumbers(widget));
requestAnimationFrame(() => {
if (widget.mrmthAceEditor) {
return;
}
loadAce()
.then(() => attachAceEditor(widget))
.catch(() => attachLineNumbers(widget));
});
}
app.registerExtension({
name: "mrmth.ScriptTextInput.LineNumbers",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (!isTargetNode(nodeData)) {
return;
}
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated?.apply(this, arguments);
attachLineNumbersWithRetry(this);
};
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments);
attachLineNumbersWithRetry(this);
};
},
});