UNPKG

sunrize

Version:

Sunrize — A Multi-Platform X3D Editor

1,091 lines (915 loc) 34.1 kB
"use strict"; const $ = require ("jquery"), electron = require ("electron"), path = require ("path"), url = require ("url"), fs = require ("fs"), X3D = require ("../X3D"), Interface = require ("../Application/Interface"), Splitter = require ("../Controls/Splitter"), NodeList = require ("./NodeList"), Console = require ("./Console"), Editor = require ("../Undo/Editor"), UndoManager = require ("../Undo/UndoManager"), monacoLoader = require ("monaco-editor/min/vs/loader.js"), _ = require ("../Application/GetText"); require ("../Controls/RenameNodeInput"); monacoLoader .require .config ({ baseUrl: url .pathToFileURL (path .resolve (path .dirname (require .resolve ("monaco-editor/package.json")), "min")) + "/", }); module .exports = class ScriptEditor extends Interface { constructor (element) { super (`Sunrize.ScriptEditor.${element .attr ("id")}.`); this .scriptEditor = element; this .verticalSplitter = $("<div></div>") .attr ("id", "script-editor-left") .addClass (["script-editor-left", "vertical-splitter"]) .appendTo (this .scriptEditor); this .verticalSplitterLeft = $("<div></div>") .addClass ("vertical-splitter-left") .css ("width", "30%") .appendTo (this .verticalSplitter); this .verticalSplitterRight = $("<div></div>") .addClass ("vertical-splitter-right") .css ("width", "70%") .appendTo (this .verticalSplitter); this .vSplitter = new Splitter (this .verticalSplitter, "vertical"); this .verticalSplitter .on ("position", () => this .onSplitterPosition ()); this .toolbar = $("<div></div>") .addClass (["toolbar", "vertical-toolbar", "script-editor-toolbar"]) .appendTo (this .scriptEditor); this .toggleSidebarButton = $("<span></span>") .addClass (["material-symbols-outlined"]) .attr ("title", _("Toggle sidebar.")) .text ("dock_to_right") .appendTo (this .toolbar) .on ("click", () => this .toggleSidebar ()); $("<span></span>") .addClass ("separator") .appendTo (this .toolbar); this .createButton = $("<span></span>") .addClass ("material-icons") .attr ("title", _("Create new Script node or shader.")) .text ("add") .appendTo (this .toolbar) .on ("click", () => this .create ()); this .applyButton = $("<span></span>") .addClass ("material-icons") .attr ("title", _("Apply script source to node.")) .text ("check_circle") .appendTo (this .toolbar) .on ("click", () => this .apply (true)); $("<span></span>") .addClass ("separator") .appendTo (this .toolbar); this .directOutputButton = $("<span></span>") .addClass ("material-icons") .attr ("title", _("Toggle direct output.")) .text ("radio_button_checked") .appendTo (this .toolbar) .on ("click", () => this .toggleDirectOutput ()); this .mustEvaluateButton = $("<span></span>") .addClass ("material-icons") .attr ("title", _("Toggle must evaluate.")) .text ("priority_high") .appendTo (this .toolbar) .on ("click", () => this .toggleMustEvaluate ()); this .shaderTypeButton = $("<span></span>") .addClass ("material-icons") .attr ("title", _("Change shader type.")) .text ("auto_awesome") .appendTo (this .toolbar) .on ("click", () => this .changeShaderType ()); this .horizontalSplitterTop = $("<div></div>") .addClass ("horizontal-splitter-top") .css ("height", "50%") .appendTo (this .verticalSplitterLeft); this .horizontalSplitterBottom = $("<div></div>") .addClass ("horizontal-splitter-bottom") .css ("height", "50%") .appendTo (this .verticalSplitterLeft); this .hSplitter = new Splitter (this .verticalSplitterLeft, "horizontal"); this .nodeListElement = $("<div></div>") .addClass (["alternating", "node-list"]) .appendTo (this .horizontalSplitterTop); this .nodeName = $("<input></input>") .addClass ("node-name") .attr ("title", _("Rename node.")) .attr ("placeholder", _("Enter node name.")) .appendTo (this .horizontalSplitterTop) .renameNodeInput (null); this .nodeList = new NodeList (this .nodeListElement, { filter: node => node .getTypeName () .match (/^(?:Script|ShaderPart)$/), callback: node => this .setNode (node), }); this .consoleElement = $("<div></div>") .attr ("id", "script-editor-console") .addClass ("console") .appendTo (this .horizontalSplitterBottom); this .console = new Console (this .consoleElement); electron .ipcRenderer .on ("script-editor", (event, key, ... args) => this [key] (... args)); // Setup. this .addLanguages (); this .setup (); } getMonaco () { return new Promise (resolve => { monacoLoader .require (["vs/editor/editor.main"], resolve); }); } async colorScheme (shouldUseDarkColors) { const monaco = await this .getMonaco (); monaco .editor .setTheme (shouldUseDarkColors ? "vs-dark" : "vs-light"); } async addLanguages () { const monaco = await this .getMonaco (); const conf = { comments: { lineComment: "//", blockComment: ["/*", "*/"], }, brackets: [ ["{", "}"], ["[", "]"], ["(", ")"], ], autoClosingPairs: [ { open: "[", close: "]" }, { open: "{", close: "}" }, { open: "(", close: ")" }, { open: "'", close: "'", notIn: ["string", "comment"] }, { open: '"', close: '"', notIn: ["string"] }, ], surroundingPairs: [ { open: "{", close: "}" }, { open: "[", close: "]" }, { open: "(", close: ")" }, { open: '"', close: '"' }, { open: "'", close: "'" }, ] }; const keywords = [ "break", "case", "const", "continue", "discard", "do", "else", "flat", "for", "highp", "if", "in", "inout", "invariant", "lowp", "mediump", "out", "precision", "return", "smooth", "struct", "switch", "uniform", "while", ]; const types = [ "bool", "bvec2", "bvec3", "bvec4", "float", "int", "ivec2", "ivec3", "ivec4", "mat2", "mat2x2", "mat2x3", "mat2x4", "mat3", "mat3x2", "mat3x3", "mat3x4", "mat4", "mat4x2", "mat4x3", "mat4x4", "sampler2D", "sampler3D", "samplerCube", "uint", "uvec2", "uvec3", "uvec4", "vec2", "vec3", "vec4", "void", ]; const functions = [ "abs", "acos", "acosh", "all", "any", "asin", "asinh", "atan", "atanh", "ceil", "clamp", "cos", "cosh", "cross ", "degrees", "determinant", "dFdx", "dFdy", "distance", "dot", "equal", "exp", "exp2", "faceforward", "floatBitsToInt", "floatBitsToUint", "floor", "fract", "fwidth", "greaterThan", "greaterThanEqual", "intBitsToFloat", "inverse", "inversesqrt", "isinf", "isnan", "length", "lessThan ", "lessThanEqual", "log", "log2", "main", "matrixCompMult", "max", "min", "mix", "mod", "modf", "normalize", "not", "notEqual", "outerProduct", "packUnorm2x16", "pow", "radians", "reflect", "refract", "round", "roundEven", "sign", "sin", "sinh", "smoothstep", "sqrt", "step", "tan", "tanh", "texelFetch", "texelFetchOffset", "texture", "textureGrad", "textureGradOffset", "textureLod", "textureLodOffset", "textureProj", "textureProjGrad", "textureProjLod", "textureProjLodOffset", "textureSize", "transpose", "trunc", "uintBitsToFloat", ]; const constants = [ "false", "true", ]; const builtins = [ "gl_ClipDistance", "gl_CullDistance", "gl_FragCoord", "gl_FragDepth", "gl_FrontFacing", "gl_GlobalInvocationID", "gl_HelperInvocation", "gl_InstanceID", "gl_InvocationID", "gl_Layer", "gl_LocalInvocationID", "gl_LocalInvocationIndex", "gl_NumSamples", "gl_NumWorkGroups", "gl_PatchVerticesIn", "gl_PointCoord", "gl_PointSize", "gl_Position", "gl_PrimitiveID", "gl_PrimitiveIDIn", "gl_SampleID", "gl_SampleMask", "gl_SampleMaskIn", "gl_SamplePosition", "gl_TessCoord", "gl_TessLevelInner", "gl_TessLevelOuter", "gl_VertexID", "gl_ViewportIndex", "gl_WorkGroupID", "gl_WorkGroupSize", ]; const language = { tokenPostfix: ".glsl", // Set defaultToken to invalid to see what you do not tokenize yet defaultToken: "invalid", keywords, types, functions, constants, builtins, operators: [ "=", ">", "<", "!", "~", "?", ":", "==", "<=", ">=", "!=", "&&", "||", "++", "--", "+", "-", "*", "/", "&", "|", "^", "%", "<<", ">>", ">>>", "+=", "-=", "*=", "/=", "&=", "|=", "^=", "%=", "<<=", ">>=", ">>>=", ], symbols: /[=><!~?:&|+\-*\/\^%]+/, escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/, integersuffix: /([uU](ll|LL|l|L)|(ll|LL|l|L)?[uU]?)/, floatsuffix: /[fFlL]?/, encoding: /u|u8|U|L/, tokenizer: { root: [ // data:mime-type, [/data:x-shader\/x-(?:vertex|fragment),/, "string"], // x3d_SpecialVariable [/x3d_\w+/, "keyword"], // identifiers and keywords [/[a-zA-Z_]\w*/, { cases: { "@keywords": { token: "keyword.$0" }, "@types": { token: "type.identifier" }, "@functions": { token: "attribute.name" }, "@constants": { token: "constant" }, "@builtins": { token: "regexp" }, "@default": "identifier", } }], // Version [/#version\s+\d+\s+es/, "keyword.directive"], // Preprocessor directive (#define) [/^\s*#\s*\w+/, "keyword.directive"], // whitespace { include: "@whitespace" }, // delimiters and operators [/[{}()\[\]]/, "@brackets"], [/@symbols/, { cases: { "@operators": "operator", "@default": "", } }], // numbers [/\d*\d+[eE]([\-+]?\d+)?(@floatsuffix)/, "number.float"], [/\d*\.\d+([eE][\-+]?\d+)?(@floatsuffix)/, "number.float"], [/0[xX][0-9a-fA-F']*[0-9a-fA-F](@integersuffix)/, "number.hex"], [/0[0-7']*[0-7](@integersuffix)/, "number.octal"], [/0[bB][0-1']*[0-1](@integersuffix)/, "number.binary"], [/\d[\d']*\d(@integersuffix)/, "number"], [/\d(@integersuffix)/, "number"], // delimiter: after number because of .\d floats [/[;,.]/, "delimiter"], ], comment: [ [/[^\/*]+/, "comment"], [/\/\*/, "comment", "@push"], ["\\*/", "comment", "@pop"], [/[\/*]/, "comment"], ], whitespace: [ [/[ \t\r\n]+/, "white"], [/\/\*/, "comment", "@comment"], [/\/\/.*$/, "comment"], ], } }; monaco .languages .register ({ id: "glsl", mimetypes: ["x-shader/x-vertex", "x-shader/x-fragment"] }); monaco .languages .setMonarchTokensProvider ("glsl", language); monaco .languages .setLanguageConfiguration ("glsl", conf); } async setNode (node) { this .directOutputButton .hide (); this .mustEvaluateButton .hide (); this .shaderTypeButton .hide (); if (this .node) { this .node ._url .removeFieldCallback (this); this .node ._loadState .removeFieldCallback (this); for (const field of this .node .getUserDefinedFields ()) field .removeInterest ("updateDeclarations", this); switch (this .node .getTypeName ()) { case "Script": { this .node ._directOutput .removeInterest ("set_directOutput", this); this .node ._mustEvaluate .removeInterest ("set_mustEvaluate", this); break; } case "ShaderPart": { this .node ._type .removeInterest ("set_shaderType", this); break; } } if (this .editor) this .editor .viewState = this .editor .saveViewState (); } this .node = node; if (this .node) { const editor = await this .getEditor (this .node); this .nodeName .renameNodeInput (this .node); this .applyButton .show (); this .monaco ?.detach (); this .monaco = editor .element .appendTo (this .verticalSplitterRight); this .editor = editor .editor; this .editor .restoreViewState (this .editor .viewState); this .node ._url .addFieldCallback (this, this .set_url .bind (this)); this .node ._loadState .addFieldCallback (this, this .set_loadState .bind (this, editor .monaco)); this .set_url (); this .set_loadState (editor .monaco); switch (this .node .getTypeName ()) { case "Script": { this .directOutputButton .show (); this .mustEvaluateButton .show (); this .node ._directOutput .addInterest ("set_directOutput", this); this .node ._mustEvaluate .addInterest ("set_mustEvaluate", this); this .set_directOutput (); this .set_mustEvaluate (); break } case "ShaderPart": { this .shaderTypeButton .show (); this .node ._type .addInterest ("set_shaderType", this); this .set_shaderType (); break; } } } else { this .nodeName .renameNodeInput (null); this .applyButton .hide (); this .monaco ?.detach (); this .monaco = null; this .editor = null; } } #internalTypes = new Map ([ [X3D .X3DConstants .SFBool, "boolean"], [X3D .X3DConstants .SFDouble, "number"], [X3D .X3DConstants .SFFloat, "number"], [X3D .X3DConstants .SFInt32, "number"], [X3D .X3DConstants .SFString, "string"], [X3D .X3DConstants .SFTime, "number"], ]); #declarations; updateDeclarations (monaco) { if (!this .node .getType () .includes (X3D .X3DConstants .Script)) return; this .#declarations ??= fs .readFileSync (X3D .TYPE_SCRIPT_PATH, "utf8") .replace (/^.*?(?:export.*?;)/s, ""); const fields = Array .from (this .node .getUserDefinedFields (), field => { if (field .getAccessType () === X3D .X3DConstants .inputOnly) return ""; const accessType = [ ]; if (field .isInput ()) accessType .push ("in"); if (field .isOutput ()) accessType .push ("out"); let value = ""; if (field instanceof X3D .X3DArrayField) { value += `(${field .length} elements)`; } else { const STRING_MAX = 16; if (field instanceof X3D .SFString) value += `"${X3D .SFString .escape (field .valueOf ()) .substring (0, STRING_MAX)}${field .length <= STRING_MAX ? "" : "..."}"`; else value += String (field); } let string = ""; string += `/** This is the user-defined field ${field .getTypeName ()} [${accessType .join (", ")}] *${field .getName ()}* ${value}. */\n`; string += `declare let ${field .getName ()}: `; switch (field .getType ()) { case X3D .X3DConstants .SFNode: { if (field .getValue ()) string += `X3D .${field .getNodeTypeName ()}Proxy;`; else string += `X3D .SFNode | null;`; break; } case X3D .X3DConstants .MFNode: { const types = Array .from (new Set (Array .from (field, node => node ? `${node .getNodeTypeName ()}Proxy` : "null"))); if (types .length) string += `X3D .MFNode <${types .join ("|")}|null>;`; else string += `X3D .MFNode;`; break; } default: { string += this .#internalTypes .get (field .getType ()) ?? `X3D .${field .getTypeName ()}`; string += ";"; break; } } return string; }); monaco .languages .typescript .javascriptDefaults .setExtraLibs ([ { content: /* ts */ ` ${this .#declarations}; declare const Browser: X3D .X3DBrowser; declare const X3DConstants: X3D .X3DConstants; declare const X3DBrowser: typeof X3D. X3DBrowser; declare const X3DExecutionContext: typeof X3D. X3DExecutionContext; declare const X3DScene: typeof X3D. X3DScene; declare const ComponentInfo: typeof X3D. ComponentInfo; declare const ComponentInfoArray: typeof X3D. ComponentInfoArray; declare const ProfileInfo: typeof X3D. ProfileInfo; declare const ProfileInfoArray: typeof X3D. ProfileInfoArray; declare const ConcreteNodesArray: typeof X3D. ConcreteNodesArray; declare const AbstractNodesArray: typeof X3D. AbstractNodesArray; declare const UnitInfo: typeof X3D. UnitInfo; declare const UnitInfoArray: typeof X3D. UnitInfoArray; declare const NamedNodesArray: typeof X3D. NamedNodesArray; declare const ImportedNodesArray: typeof X3D. ImportedNodesArray; declare const X3DImportedNode: typeof X3D. X3DImportedNode; declare const ExportedNodesArray: typeof X3D. ExportedNodesArray; declare const X3DExportedNode: typeof X3D. X3DExportedNode; declare const ExternProtoDeclarationArray: typeof X3D. ExternProtoDeclarationArray; declare const ProtoDeclarationArray: typeof X3D. ProtoDeclarationArray; declare const X3DExternProtoDeclaration: typeof X3D. X3DExternProtoDeclaration; declare const X3DProtoDeclaration: typeof X3D. X3DProtoDeclaration; declare const X3DProtoDeclarationNode: typeof X3D. X3DProtoDeclarationNode; declare const RouteArray: typeof X3D. RouteArray; declare const X3DRoute: typeof X3D. X3DRoute; declare const X3DFieldDefinition: typeof X3D. X3DFieldDefinition; declare const FieldDefinitionArray: typeof X3D. FieldDefinitionArray; declare const X3DField: typeof X3D. X3DField; declare const X3DArrayField: typeof X3D. X3DArrayField; ${Array .from (this .browser .fieldTypes) .filter (type => !this .#internalTypes .has (type .type)) .map (type => `declare const ${type .typeName}: typeof X3D .${type .typeName};`) .join ("\n")} declare const TRUE: true; declare const FALSE: false; declare const NULL: null; declare function print (... args: any []): void; ${fields .join ("\n")}; `, }]); } editors = new Map (); languages = { "Script": "javascript", "ShaderPart": "glsl", }; async getEditor (node) { if (!this .editors .has (node)) this .editors .set (node, await this .createEditor (node)); return this .editors .get (node); } async createEditor (node) { const monaco = await this .getMonaco (), element = $("<div></div>") .addClass ("script-editor-monaco"); const editor = monaco .editor .create (element .get (0), { automaticLayout: true, bracketPairColorization: { enabled: true }, contextmenu: false, detectIndentation: true, language: this .languages [node .getTypeName ()], minimap: { enabled: false }, tabSize: 3, wordWrap: "on", wrappingIndent: "indent", }); editor .viewState = editor .saveViewState (); editor .onDidFocusEditorWidget (() => this .updateDeclarations (monaco)); editor .onDidBlurEditorWidget (() => this .apply ()); editor .onKeyDown (event => this .onKeyDown (event)); element .on ("mouseenter", () => this .updateDeclarations (monaco)); element .on ("contextmenu", () => this .showContextMenu ()); // this .debugFindActions (editor) return { element, editor, monaco }; } onKeyDown (event) { const { keyCode, ctrlKey, metaKey } = event; switch (keyCode) { case 33: // c { if (!(metaKey || ctrlKey)) break; event .preventDefault (); event .stopPropagation (); this .cutOrCopy ("copy"); break; } case 52: // v { if (!(metaKey || ctrlKey)) break; event .preventDefault (); event .stopPropagation (); this .paste (); break; } case 54: // x { if (!(metaKey || ctrlKey)) break; event .preventDefault (); event .stopPropagation (); this .cutOrCopy ("cut"); break; } } } async showContextMenu () { await $.sleep (); const app = require ("../Application/Window"); if (!app .activeElementIsMonacoEditor ()) return; const menu = [ // { // label: _("Go to Definition"), // args: ["runAction", "editor.action.revealDefinition"], // }, // { // label: _("Go to References"), // args: ["runAction", "editor.action.goToReferences"], // }, // { // label: _("Go to Symbol..."), // args: ["runAction", "editor.action.quickOutline"], // }, // { // label: _("Peek"), // submenu: [ // { // label: _("Peek Definition"), // args: ["runAction", "editor.action.peekDefinition"], // }, // { // label: _("Peek References"), // args: ["runAction", "editor.action.referenceSearch.trigger"], // }, // ], // }, { type: "separator" }, { label: _("Rename Symbol"), args: ["runAction", "editor.action.rename"], }, { label: _("Change All Occurrences"), args: ["runAction", "editor.action.changeAll"], }, { label: _("Format Document"), args: ["runAction", "editor.action.formatDocument"], }, { type: "separator" }, { label: _("Cut"), accelerator: "CmdOrCtrl+X", args: ["cutOrCopy", "cut"], }, { label: _("Copy"), accelerator: "CmdOrCtrl+C", args: ["cutOrCopy", "copy"], }, { label: _("Paste"), accelerator: "CmdOrCtrl+V", args: ["paste"], }, { type: "separator" }, { label: _("Command Palette"), args: ["runAction", "editor.action.quickCommand"], }, ]; electron .ipcRenderer .send ("context-menu", "script-editor", menu); } runAction (id) { this .editor .getAction (id) .run (); } triggerEvent (... args) { this .editor .trigger (... args); } // execCommand (command) // { // document .execCommand (command); // } debugFindActions (editor = this .editor) { for (const action of editor .getSupportedActions ()) { if (action .label .match (/comment/i)) console .log (action .label, action .id); switch (action .label) { case "Go to Definition": // editor.action.revealDefinition console .log (action .label, action .id); break; case "Go to References": // editor.action.goToReferences console .log (action .label, action .id); break; case "Go to Symbol...": // editor.action.quickOutline console .log (action .label, action .id); break; case "Peek Definition": // editor.action.peekDefinition console .log (action .label, action .id); break; case "Peek References": // editor.action.referenceSearch.trigger console .log (action .label, action .id); break; case "Rename Symbol": // editor.action.rename console .log (action .label, action .id); break; case "Change All Occurrences": // editor.action.changeAll console .log (action .label, action .id); break; case "Format Document": // editor.action.formatDocument console .log (action .label, action .id); break; case "Cut": // console .log (action .label, action .id); break; case "Copy": // console .log (action .label, action .id); break; case "Paste": // console .log (action .label, action .id); break; case "Command Palette": // editor.action.quickCommand console .log (action .label, action .id); break; } } } cutOrCopy (type) { this .editor .focus (); // Get the current selection in the editor. const selection = this .editor .getSelection (); if (!selection || selection .isEmpty ()) { navigator .clipboard .writeText (""); return; } // Get the text from that selection. const data = this .editor .getModel () ?.getValueInRange (selection); // Set the clipboard contents. navigator .clipboard .writeText (data || ""); if (type === "copy") return; // This is a cut operation, so replace the selection with an empty string. this .editor .executeEdits ("clipboard", [{ range: selection, text: "", forceMoveMarkers: true, }]); } async paste () { this .editor .focus (); // Get the current clipboard contents const text = await navigator .clipboard .readText (); // Get the current selection in the editor. const selection = this .editor .getSelection (); if (!selection) return; // Replace the current contents with the text from the clipboard. this .editor .executeEdits ("clipboard", [{ range: selection, text: text, forceMoveMarkers: true, }]); } create () { const menu = [ { label: _("Create New Script"), args: ["createScript"], }, { label: _("Create New Shader"), args: ["createShader"], }, ]; electron .ipcRenderer .send ("context-menu", "script-editor", menu); } async createScript () { UndoManager .shared .beginUndo (_("Create New Script")); await Editor .addComponent (this .browser .currentScene, "Scripting"); const nodes = await Editor .importX3D (this .browser .currentScene, ` DEF NewScript Script { url "ecmascript: function initialize () { // Add code here. } function set_field (value, time) { print (time, value); } " directOutput TRUE } `); UndoManager .shared .endUndo (); this .nodeList .setNode (nodes [0]); } async createShader () { UndoManager .shared .beginUndo (_("Create New Shader")); await Editor .addComponent (this .browser .currentScene, "Shaders"); const nodes = await Editor .importX3D (this .browser .currentScene, ` DEF NewShader ComposedShader { language "GLSL" parts [ DEF VertexShader ShaderPart { type "VERTEX" url "data:x-shader/x-vertex,#version 300 es precision highp float; precision highp int; uniform mat4 x3d_ProjectionMatrix; uniform mat4 x3d_ModelViewMatrix; in vec4 x3d_Vertex; void main () { gl_Position = x3d_ProjectionMatrix * x3d_ModelViewMatrix * x3d_Vertex; } " } DEF FragmentShader ShaderPart { type "FRAGMENT" url "data:x-shader/x-fragment,#version 300 es precision highp float; precision highp int; out vec4 x3d_FragColor; void main () { x3d_FragColor = vec4 (1.0, 0.0, 0.0, 1.0); } " } ] } `); UndoManager .shared .endUndo (); this .nodeList .setNode (nodes [0] ._parts [0] .getValue ()); } apply (force) { if (!this .node) return; if (!this .editor) return; const string = this .editor .getModel () .getValue (), value = this .node ._url .toSpliced (0, 1, Editor .encodeURI (string)); if (!this .node ._url .equals (value)) { this .node ._url .addFieldCallback (this, () => this .node ._url .addFieldCallback (this, this .set_url .bind (this))); Editor .setFieldValue (this .node .getExecutionContext (), this .node, this .node ._url, value); } if (!force) return; this .node ._url .addFieldCallback (this, () => this .node ._url .addFieldCallback (this, this .set_url .bind (this))); this .node ._url = value; Editor .requestUpdateInstances (this .node); } set_url () { this .editor .getModel () .setValue (Editor .decodeURI (this .node ._url [0])); } set_loadState (monaco) { this .applyButton .removeClass (["red", "green", "yellow"]); switch (this .node .checkLoadState ()) { case X3D .X3DConstants .NOT_STATED_STATE: break; case X3D .X3DConstants .IN_PROGRESS_STATE: this .applyButton .addClass ("yellow"); break; case X3D .X3DConstants .COMPLETE_STATE: this .applyButton .addClass ("green"); break; case X3D .X3DConstants .FAILED_STATE: this .applyButton .addClass ("red"); break; } for (const field of this .node .getUserDefinedFields ()) { switch (field .getType ()) { case X3D .X3DConstants .SFNode: case X3D .X3DConstants .MFNode: { field .addInterest ("updateDeclarations", this, monaco); break; } } } this .updateDeclarations (monaco); } toggleDirectOutput () { Editor .setFieldValue (this .node .getExecutionContext (), this .node, this .node ._directOutput, !this .node ._directOutput .getValue ()); } set_directOutput () { if (this .node ._directOutput .getValue ()) this .directOutputButton .addClass ("active"); else this .directOutputButton .removeClass ("active"); } toggleMustEvaluate () { Editor .setFieldValue (this .node .getExecutionContext (), this .node, this .node ._mustEvaluate, !this .node ._mustEvaluate .getValue ()); } set_mustEvaluate () { if (this .node ._mustEvaluate .getValue ()) this .mustEvaluateButton .addClass ("active"); else this .mustEvaluateButton .removeClass ("active"); } changeShaderType () { const menu = [ { label: "VERTEX", args: ["changeShaderTypeTo", "VERTEX"], }, { label: "FRAGMENT", args: ["changeShaderTypeTo", "FRAGMENT"], }, ]; electron .ipcRenderer .send ("context-menu", "script-editor", menu); } changeShaderTypeTo (type) { Editor .setFieldValue (this .node .getExecutionContext (), this .node, this .node ._type, type); } set_shaderType () { switch (this .node ._type .getValue ()) { case "VERTEX": { this .shaderTypeButton .text ("timeline"); break; } case "FRAGMENT": { this .shaderTypeButton .text ("auto_awesome"); break; } } } onSplitterPosition () { if (this .vSplitter .position) this .toggleSidebarButton .addClass ("active"); else this .toggleSidebarButton .removeClass ("active"); } toggleSidebar () { if (this .vSplitter .position) { this .config .file .vSplitterPosition = this .vSplitter .position; this .vSplitter .position = 0; } else { this .vSplitter .position = this .config .file .vSplitterPosition; } } };