d?(a.sortIndex=c,f(t,a),null===h(r)&&a===h(t)&&(B?(E(L),L=-1):B=!0,K(H,c-d))):(a.sortIndex=e,f(r,a),A||z||(A=!0,I(J)));return a};\nexports.unstable_shouldYield=M;exports.unstable_wrapCallback=function(a){var b=y;return function(){var c=y;y=b;try{return a.apply(this,arguments)}finally{y=c}}};\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/scheduler.production.min.js');\n} else {\n module.exports = require('./cjs/scheduler.development.js');\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","// extracted by mini-css-extract-plugin\nexport default {\"menuBar\":\"MenuBar_menuBar__XCTFS\",\"dark\":\"MenuBar_dark__nb3tv\",\"light\":\"MenuBar_light__hSyRq\",\"menuItem\":\"MenuBar_menuItem__W6ii4\",\"menuButton\":\"MenuBar_menuButton__9XVop\",\"active\":\"MenuBar_active__7j403\",\"dropdownMenu\":\"MenuBar_dropdownMenu__7Qbic\",\"menuItemButton\":\"MenuBar_menuItemButton__4dqRJ\"};","import React from 'react';\nimport styles from './MenuBar.module.css';\n\nconst MenuBar = ({\n menuOpen,\n handleMenuClick,\n handleMenuItemClick,\n config,\n toggleTheme,\n toggleNodeRounding\n}) => {\n const theme = config.isDarkTheme ? 'dark' : 'light';\n\n return (\n \n {['File', 'Edit', 'View', 'Export', 'Run', 'Help'].map((menu) => (\n
\n
\n {menuOpen === menu && (\n
\n {menu === 'File' && [\n ,\n ,\n ,\n ]}\n {menu === 'Edit' && [\n ,\n ,\n ,\n ,\n ,\n ,\n \n ]}\n {menu === 'View' && [\n ,\n ,\n ,\n ,\n ,\n ,\n ,\n \n ]}\n {menu === 'Export' && [\n ,\n ,\n ,\n \n ]}\n {menu === 'Run' && [\n ,\n ,\n \n ]}\n {menu === 'Help' && [\n ,\n ,\n ,\n ,\n \n ]}\n
\n )}\n
\n ))}\n
\n );\n};\n\nexport default MenuBar;","export const nodeTypes = {\n OnStart: {\n color: '#4CAF50',\n inputs: [],\n outputs: [{\n type: 'control',\n name: 'Next',\n description: 'Triggered when the script begins execution'\n }],\n description: 'Triggered when the script starts'\n },\n Log: {\n color: '#FF5722',\n inputs: [\n {\n type: 'control',\n name: 'In',\n description: 'Triggers the log operation'\n },\n {\n type: 'data',\n name: 'Message',\n description: 'The message to be logged'\n }\n ],\n outputs: [{\n type: 'control',\n name: 'Out',\n description: 'Triggered after the message is logged'\n }],\n description: 'Logs a message to the console',\n properties: [\n { name: 'message', type: 'string', default: '' },\n { name: 'logType', type: 'select', options: ['log', 'error', 'warn', 'info'], default: 'log' }\n ]\n },\n Variable: {\n color: '#795548',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Set' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Get' }\n ],\n description: 'Stores and retrieves a value',\n properties: [\n { name: 'name', type: 'string', default: 'myVariable' },\n { name: 'type', type: 'select', options: ['string', 'number', 'boolean', 'object', 'array'], default: 'string' },\n { name: 'initialValue', type: 'string', default: '' }\n ]\n },\n Function: {\n color: '#607D8B',\n inputs: [\n { type: 'control', name: 'In' },\n {\n type: 'data',\n name: 'param1',\n description: 'Parameter of type string'\n }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Return' }\n ],\n description: 'Defines a reusable function',\n properties: [\n { name: 'name', type: 'string', default: 'myFunction' },\n {\n name: 'parameters',\n type: 'array',\n default: [\n { name: 'param1', type: 'string' }\n ],\n visible: true\n },\n {\n name: 'returnType',\n type: 'select',\n options: ['void', 'string', 'number', 'boolean', 'object', 'array'],\n default: 'void',\n visible: true\n }\n ]\n },\n MathOperation: {\n color: '#9C27B0',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'A' },\n { type: 'data', name: 'B' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Result' }\n ],\n description: 'Performs a mathematical operation',\n properties: [\n { name: 'operation', type: 'select', options: ['+', '-', '*', '/', '%', '**'], default: '+' }\n ]\n },\n Condition: {\n color: '#00BCD4',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Condition' }\n ],\n outputs: [\n { type: 'control', name: 'True' },\n { type: 'control', name: 'False' }\n ],\n description: 'Branches based on a condition',\n properties: [\n { name: 'condition', type: 'string', default: '' }\n ]\n },\n WhileLoop: {\n color: '#2196F3',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Condition' }\n ],\n outputs: [\n { type: 'control', name: 'Loop' },\n { type: 'control', name: 'Out' }\n ],\n description: 'Repeats a set of instructions while a condition is true',\n properties: [\n { name: 'condition', type: 'string', default: 'true' }\n ]\n },\n ForLoop: {\n color: '#2196F3',\n inputs: [{ type: 'control', name: 'In' }],\n outputs: [\n { type: 'control', name: 'Loop' },\n { type: 'control', name: 'Out' }\n ],\n description: 'Repeats a set of instructions for a specified number of times',\n properties: [\n { name: 'start', type: 'number', default: 0 },\n { name: 'end', type: 'number', default: 10 },\n { name: 'step', type: 'number', default: 1 }\n ]\n },\n ArrayOperation: {\n color: '#FF9800',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Array' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Result' }\n ],\n description: 'Performs operations on arrays',\n properties: [\n { name: 'operation', type: 'select', options: ['push', 'pop', 'shift', 'unshift', 'slice', 'map', 'filter', 'reduce'], default: 'push' },\n { name: 'argument', type: 'string', default: '' }\n ]\n },\n ObjectOperation: {\n color: '#9E9E9E',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Object' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Result' }\n ],\n description: 'Performs operations on objects',\n properties: [\n { name: 'operation', type: 'select', options: ['get', 'set', 'delete', 'has'], default: 'get' },\n { name: 'key', type: 'string', default: '' },\n { name: 'value', type: 'string', default: '' }\n ]\n },\n HttpRequest: {\n color: '#E91E63',\n inputs: [{ type: 'control', name: 'In' }],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Response Body' },\n { type: 'data', name: 'Status Code' },\n { type: 'data', name: 'Headers' },\n ],\n description: 'Sends an HTTP request',\n properties: [\n { name: 'url', type: 'string', default: '' },\n { name: 'method', type: 'select', options: ['GET', 'POST', 'PUT', 'DELETE'], default: 'GET' },\n { name: 'headers', type: 'string', default: '{}' },\n { name: 'body', type: 'string', default: '' }\n ]\n },\n JSONParse: {\n color: '#4CAF50',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'JSON String' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Parsed Object' }\n ],\n description: 'Parses a JSON string into an object',\n properties: []\n },\n JSONStringify: {\n color: '#4CAF50',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Object' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'JSON String' }\n ],\n description: 'Converts an object to a JSON string',\n properties: [\n { name: 'space', type: 'number', default: 0 }\n ]\n },\n Base64Encode: {\n color: '#9C27B0',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Input' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Encoded' }\n ],\n description: 'Encodes a string to Base64',\n properties: []\n },\n Base64Decode: {\n color: '#9C27B0',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'Encoded' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Decoded' }\n ],\n description: 'Decodes a Base64 string',\n properties: []\n },\n If: {\n color: '#00BCD4',\n inputs: [\n { type: 'control', name: 'In' },\n { type: 'data', name: 'A' },\n { type: 'data', name: 'B' }\n ],\n outputs: [\n { type: 'control', name: 'True' },\n { type: 'control', name: 'False' }\n ],\n description: 'Compares two values and branches based on the condition',\n properties: [\n { name: 'operator', type: 'select', options: ['==', '===', '!=', '!==', '>', '<', '>=', '<='], default: '==' }\n ]\n },\n Random: {\n color: '#9C27B0',\n inputs: [\n { type: 'control', name: 'In' }\n ],\n outputs: [\n { type: 'control', name: 'Out' },\n { type: 'data', name: 'Result' }\n ],\n description: 'Generates random values of different types',\n properties: [\n {\n name: 'type',\n type: 'select',\n options: ['number', 'string', 'boolean'],\n default: 'number',\n visible: true\n },\n {\n name: 'min',\n type: 'number',\n default: 1,\n visible: (props) => props.type === 'number'\n },\n {\n name: 'max',\n type: 'number',\n default: 100,\n visible: (props) => props.type === 'number'\n },\n {\n name: 'length',\n type: 'number',\n default: 10,\n visible: (props) => props.type === 'string'\n },\n {\n name: 'charset',\n type: 'string',\n default: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',\n visible: (props) => props.type === 'string'\n },\n {\n name: 'probability',\n type: 'number',\n default: 50,\n visible: (props) => props.type === 'boolean'\n }\n ]\n },\n Switch: {\n color: '#FF9800',\n inputs: [\n {\n type: 'control',\n name: 'In',\n description: 'Triggers the switch evaluation'\n },\n {\n type: 'data',\n name: 'Value',\n description: 'The value to switch on'\n }\n ],\n outputs: [\n {\n type: 'control',\n name: 'Default',\n description: 'Triggered if no cases match'\n }\n ],\n description: 'Branches based on matching cases',\n properties: [\n {\n name: 'cases',\n type: 'array',\n default: [\n { value: '', type: 'string', output: 'Case 1' }\n ],\n visible: true\n },\n {\n name: 'ignoreCase',\n type: 'boolean',\n default: false,\n visible: true\n }\n ]\n }\n};\n\nexport const nodeGroups = {\n \"Control Flow\": [\"OnStart\", \"If\", \"Condition\", \"WhileLoop\", \"ForLoop\", \"Switch\"],\n \"Data Manipulation\": [\"Variable\", \"MathOperation\", \"ArrayOperation\", \"ObjectOperation\", \"JSONParse\", \"JSONStringify\", \"Random\"],\n \"Functions\": [\"Function\"],\n \"Input/Output\": [\"Log\", \"HttpRequest\"],\n \"Encoding\": [\"Base64Encode\", \"Base64Decode\"]\n};","// extracted by mini-css-extract-plugin\nexport default {\"contextMenu\":\"ContextMenu_contextMenu__P39AW\",\"mainMenu\":\"ContextMenu_mainMenu__Qj8r5\",\"submenu\":\"ContextMenu_submenu__RHRy4\",\"groupButton\":\"ContextMenu_groupButton__wdm3k\",\"nodeButton\":\"ContextMenu_nodeButton__VxRt-\",\"arrow\":\"ContextMenu_arrow__tMz2L\",\"icon\":\"ContextMenu_icon__pZq4h\"};","import React, { useState, useRef, useEffect } from 'react';\r\nimport { nodeGroups } from '../nodeDefinitions';\r\nimport styles from './ContextMenu.module.css';\r\n\r\nexport const getIconForNodeType = (type) => {\r\n switch (type) {\r\n case 'OnStart': return 'fa-play';\r\n case 'Log': return 'fa-terminal';\r\n case 'Variable': return 'fa-cube';\r\n case 'Function': return 'fa-code';\r\n case 'MathOperation': return 'fa-calculator';\r\n case 'Condition': return 'fa-code-branch';\r\n case 'WhileLoop': return 'fa-sync';\r\n case 'ForLoop': return 'fa-redo';\r\n case 'ArrayOperation': return 'fa-list';\r\n case 'ObjectOperation': return 'fa-cube';\r\n case 'HttpRequest': return 'fa-globe';\r\n case 'JSONParse': return 'fa-file-code';\r\n case 'JSONStringify': return 'fa-file-alt';\r\n case 'Base64Encode': return 'fa-lock';\r\n case 'Base64Decode': return 'fa-unlock';\r\n default: return 'fa-puzzle-piece';\r\n }\r\n};\r\n\r\nconst ContextMenu = ({ visible, x, y, nodeTypes, addNode, camera }) => {\r\n const [openGroup, setOpenGroup] = useState(null);\r\n const menuRef = useRef(null);\r\n\r\n useEffect(() => {\r\n if (!visible) {\r\n setOpenGroup(null);\r\n }\r\n }, [visible]);\r\n\r\n if (!visible) return null;\r\n\r\n const handleGroupClick = (group) => {\r\n setOpenGroup(openGroup === group ? null : group);\r\n };\r\n\r\n const getIconForGroup = (group) => {\r\n switch (group) {\r\n case 'Control Flow': return 'fa-random';\r\n case 'Data Manipulation': return 'fa-database';\r\n case 'Functions': return 'fa-code';\r\n case 'Input/Output': return 'fa-exchange-alt';\r\n case 'Encoding': return 'fa-key';\r\n default: return 'fa-folder';\r\n }\r\n };\r\n\r\n const renderNodeButton = (type) => (\r\n \r\n );\r\n\r\n return (\r\n \r\n
\r\n {Object.entries(nodeGroups).map(([group, types]) => (\r\n
\r\n \r\n
\r\n ))}\r\n
\r\n {openGroup && (\r\n
\r\n {nodeGroups[openGroup].map(type => renderNodeButton(type))}\r\n
\r\n )}\r\n
\r\n );\r\n};\r\n\r\nexport default ContextMenu;","import React, { useRef, useEffect, useCallback } from 'react';\r\n\r\nconst Minimap = ({ nodes, edges, camera, canvasSize, getNodeDimensions, nodeTypes }) => {\r\n const minimapRef = useRef(null);\r\n\r\n const calculateBounds = useCallback((nodes, ctx) => {\r\n if (!nodes.length) return null;\r\n\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\r\n\r\n nodes.forEach(node => {\r\n try {\r\n const { width, height } = getNodeDimensions(node, ctx);\r\n minX = Math.min(minX, node.x);\r\n minY = Math.min(minY, node.y);\r\n maxX = Math.max(maxX, node.x + width);\r\n maxY = Math.max(maxY, node.y + height);\r\n } catch (error) {\r\n console.warn('Error calculating node dimensions:', error);\r\n }\r\n });\r\n\r\n return minX === Infinity ? null : { minX, minY, maxX, maxY };\r\n }, [getNodeDimensions]);\r\n\r\n const drawEdge = useCallback((ctx, edge, nodes, scale, bounds) => {\r\n const startNode = nodes.find(n => n.id === edge.start.nodeId);\r\n const endNode = nodes.find(n => n.id === edge.end.nodeId);\r\n if (!startNode || !endNode) return;\r\n\r\n const startDimensions = getNodeDimensions(startNode, ctx);\r\n const endDimensions = getNodeDimensions(endNode, ctx);\r\n\r\n const startPort = startNode.getPortPosition(edge.start.index, edge.start.isInput, startDimensions);\r\n const endPort = endNode.getPortPosition(edge.end.index, edge.end.isInput, endDimensions);\r\n\r\n ctx.beginPath();\r\n ctx.moveTo((startPort.x - bounds.minX) * scale, (startPort.y - bounds.minY) * scale);\r\n ctx.lineTo((endPort.x - bounds.minX) * scale, (endPort.y - bounds.minY) * scale);\r\n ctx.strokeStyle = '#666';\r\n ctx.lineWidth = 1;\r\n ctx.stroke();\r\n }, [getNodeDimensions]);\r\n\r\n const drawNode = useCallback((ctx, node, scale, bounds) => {\r\n const { width, height } = getNodeDimensions(node, ctx);\r\n const nodeType = nodeTypes[node.type];\r\n\r\n // Node body\r\n ctx.fillStyle = nodeType.color;\r\n ctx.fillRect(\r\n (node.x - bounds.minX) * scale,\r\n (node.y - bounds.minY) * scale,\r\n width * scale,\r\n height * scale\r\n );\r\n\r\n // Node outline\r\n ctx.strokeStyle = '#000000';\r\n ctx.lineWidth = 1;\r\n ctx.strokeRect(\r\n (node.x - bounds.minX) * scale,\r\n (node.y - bounds.minY) * scale,\r\n width * scale,\r\n height * scale\r\n );\r\n\r\n // Node title\r\n ctx.fillStyle = 'white';\r\n ctx.font = `bold ${8 * scale}px Arial`;\r\n ctx.fillText(\r\n node.type,\r\n (node.x - bounds.minX + 5) * scale,\r\n (node.y - bounds.minY + 15) * scale\r\n );\r\n }, [getNodeDimensions, nodeTypes]);\r\n\r\n const drawViewport = (ctx, camera, canvasSize, scale, bounds) => {\r\n const viewportWidth = canvasSize.width / camera.scale;\r\n const viewportHeight = canvasSize.height / camera.scale;\r\n\r\n ctx.strokeStyle = 'red';\r\n ctx.lineWidth = 2;\r\n ctx.strokeRect(\r\n (-camera.x - bounds.minX) * scale,\r\n (-camera.y - bounds.minY) * scale,\r\n viewportWidth * scale,\r\n viewportHeight * scale\r\n );\r\n };\r\n\r\n const drawMinimap = useCallback(() => {\r\n const minimap = minimapRef.current;\r\n if (!minimap || !nodes || !edges) return;\r\n\r\n const minimapCtx = minimap.getContext('2d');\r\n if (!minimapCtx) return;\r\n\r\n // Clear the minimap\r\n minimapCtx.fillStyle = '#1e1e1e';\r\n minimapCtx.fillRect(0, 0, minimap.width, minimap.height);\r\n\r\n // Calculate bounds\r\n const bounds = calculateBounds(nodes, minimapCtx);\r\n if (!bounds) return;\r\n\r\n // Add padding\r\n const padding = 50;\r\n bounds.minX -= padding;\r\n bounds.minY -= padding;\r\n bounds.maxX += padding;\r\n bounds.maxY += padding;\r\n\r\n // Calculate scale\r\n const scaleX = minimap.width / (bounds.maxX - bounds.minX);\r\n const scaleY = minimap.height / (bounds.maxY - bounds.minY);\r\n const scale = Math.min(scaleX, scaleY, 1);\r\n\r\n // Draw elements\r\n edges.forEach(edge => drawEdge(minimapCtx, edge, nodes, scale, bounds));\r\n nodes.forEach(node => drawNode(minimapCtx, node, scale, bounds));\r\n drawViewport(minimapCtx, camera, canvasSize, scale, bounds);\r\n\r\n }, [nodes, edges, camera, canvasSize, calculateBounds, drawEdge, drawNode]);\r\n\r\n useEffect(() => {\r\n try {\r\n drawMinimap();\r\n } catch (error) {\r\n console.error('Error drawing minimap:', error);\r\n }\r\n }, [drawMinimap]);\r\n\r\n useEffect(() => {\r\n const canvas = minimapRef.current;\r\n if (!canvas) return;\r\n\r\n const resizeObserver = new ResizeObserver(() => drawMinimap());\r\n resizeObserver.observe(canvas);\r\n return () => resizeObserver.disconnect();\r\n }, [drawMinimap]);\r\n\r\n return (\r\n \r\n );\r\n};\r\n\r\nexport default Minimap;","// extracted by mini-css-extract-plugin\nexport default {\"tabContainer\":\"Tabs_tabContainer__KnZap\",\"dark\":\"Tabs_dark__2iRQW\",\"light\":\"Tabs_light__I1M+S\",\"tab\":\"Tabs_tab__wQPEm\",\"active\":\"Tabs_active__Kk-Oc\",\"tabTitle\":\"Tabs_tabTitle__-Jeaz\",\"closeButton\":\"Tabs_closeButton__QfIMB\"};","import React from 'react';\r\nimport styles from './Tabs.module.css';\r\n\r\nconst Tabs = ({ tabs, activeTab, onTabClick, onTabClose, isDarkTheme }) => {\r\n const theme = isDarkTheme ? 'dark' : 'light';\r\n\r\n return (\r\n \r\n {tabs.map((tab) => {\r\n const isActive = activeTab === tab.id;\r\n\r\n return (\r\n
onTabClick(tab.id)}\r\n className={`${styles.tab} ${styles[theme]} ${isActive ? styles.active : ''}`}\r\n >\r\n \r\n {tab.title}\r\n \r\n {tab.id !== 'untitled-1' && (\r\n {\r\n e.stopPropagation();\r\n onTabClose(tab.id);\r\n }}\r\n className={`${styles.closeButton} ${styles[theme]} ${isActive ? styles.active : ''}`}\r\n >\r\n ×\r\n \r\n )}\r\n
\r\n );\r\n })}\r\n
\r\n );\r\n};\r\n\r\nexport default Tabs;","import React from 'react';\r\nimport './CustomCheckbox.css';\r\n\r\nconst SettingsTab = ({\r\n config,\r\n toggleTheme,\r\n toggleGrid,\r\n toggleMinimap,\r\n updateCodeGeneratorSettings,\r\n toggleNodeRounding\r\n}) => {\r\n const sectionStyle = {\r\n backgroundColor: config.isDarkTheme ? '#2d2d2d' : '#f0f0f0',\r\n padding: '20px',\r\n borderRadius: '8px',\r\n marginBottom: '20px',\r\n };\r\n\r\n const headingStyle = {\r\n color: config.isDarkTheme ? '#ffffff' : '#000000',\r\n paddingBottom: '10px',\r\n marginBottom: '20px',\r\n };\r\n\r\n const CustomCheckbox = ({ checked, onChange, label }) => (\r\n \r\n );\r\n\r\n return (\r\n \r\n
Settings
\r\n\r\n
\r\n
Theme
\r\n \r\n \r\n\r\n
\r\n
Canvas
\r\n \r\n \r\n \r\n \r\n\r\n
\r\n
Code Generator
\r\n updateCodeGeneratorSettings('useStrict', !config.codeGenerator.useStrict)}\r\n label=\"Use Strict Mode\"\r\n />\r\n updateCodeGeneratorSettings('useSemicolons', !config.codeGenerator.useSemicolons)}\r\n label=\"Use Semicolons\"\r\n />\r\n updateCodeGeneratorSettings('useConst', !config.codeGenerator.useConst)}\r\n label=\"Use Const (instead of Let)\"\r\n />\r\n updateCodeGeneratorSettings('generateComments', !config.codeGenerator.generateComments)}\r\n label=\"Generate Comments\"\r\n />\r\n \r\n
\r\n );\r\n};\r\n\r\nexport default SettingsTab;","class Camera {\r\n constructor(x = 0, y = 0, scale = 1, minZoom = 0.1, maxZoom = 5) {\r\n this.x = x;\r\n this.y = y;\r\n this.scale = scale;\r\n this.minZoom = minZoom;\r\n this.maxZoom = maxZoom;\r\n }\r\n\r\n move(dx, dy) {\r\n const zoomFactor = Math.max(0.1, Math.min(1, this.scale));\r\n const adjustedDx = (dx / this.scale) * zoomFactor;\r\n const adjustedDy = (dy / this.scale) * zoomFactor;\r\n this.x += adjustedDx;\r\n this.y += adjustedDy;\r\n }\r\n\r\n zoom(factor, centerX, centerY) {\r\n const oldScale = this.scale;\r\n this.scale *= factor;\r\n this.scale = Math.min(Math.max(this.scale, this.minZoom), this.maxZoom);\r\n\r\n // Adjust the position to zoom towards the mouse position\r\n this.x += (centerX - this.x) * (1 - this.scale / oldScale);\r\n this.y += (centerY - this.y) * (1 - this.scale / oldScale);\r\n }\r\n\r\n applyToContext(ctx) {\r\n ctx.setTransform(this.scale, 0, 0, this.scale, this.x, this.y);\r\n }\r\n\r\n screenToWorld(screenX, screenY) {\r\n return {\r\n x: (screenX - this.x) / this.scale,\r\n y: (screenY - this.y) / this.scale\r\n };\r\n }\r\n\r\n worldToScreen(worldX, worldY) {\r\n return {\r\n x: worldX * this.scale + this.x,\r\n y: worldY * this.scale + this.y\r\n };\r\n }\r\n\r\n reset() {\r\n this.x = 0;\r\n this.y = 0;\r\n this.scale = 1;\r\n }\r\n}\r\n\r\nexport default Camera;","import { nodeTypes } from '../nodeDefinitions';\nimport { getIconForNodeType } from '../components/ContextMenu';\n\nconst FONT_FAMILY = \"'Inter', sans-serif\";\n\nclass Renderer {\n constructor(camera, isDarkTheme, isGridVisible, isNodeRoundingEnabled) {\n this.camera = camera;\n this.isDarkTheme = isDarkTheme;\n this.isGridVisible = isGridVisible;\n this.isNodeRoundingEnabled = isNodeRoundingEnabled;\n this.renderDescription = false;\n this.GRID_SIZE = 20;\n }\n\n drawGrid(ctx, canvasWidth, canvasHeight) {\n if (!this.isGridVisible) return;\n\n const { x: offsetX, y: offsetY, scale } = this.camera;\n const gridSize = this.GRID_SIZE * scale;\n\n ctx.strokeStyle = '#2a2a2a';\n ctx.lineWidth = 1;\n\n const visibleLeft = -offsetX / scale;\n const visibleTop = -offsetY / scale;\n const visibleRight = (canvasWidth - offsetX) / scale;\n const visibleBottom = (canvasHeight - offsetY) / scale;\n\n const startX = Math.floor(visibleLeft / gridSize) * gridSize;\n const startY = Math.floor(visibleTop / gridSize) * gridSize;\n const endX = Math.ceil(visibleRight / gridSize) * gridSize;\n const endY = Math.ceil(visibleBottom / gridSize) * gridSize;\n\n for (let x = startX; x <= endX; x += gridSize) {\n ctx.beginPath();\n ctx.moveTo(x, visibleTop);\n ctx.lineTo(x, visibleBottom);\n ctx.stroke();\n }\n\n for (let y = startY; y <= endY; y += gridSize) {\n ctx.beginPath();\n ctx.moveTo(visibleLeft, y);\n ctx.lineTo(visibleRight, y);\n ctx.stroke();\n }\n }\n\n getNodeDimensions(node, ctx) {\n const nodeType = nodeTypes[node.type];\n const maxPorts = Math.max(nodeType.inputs.length, nodeType.outputs.length);\n\n ctx.font = `600 14px ${FONT_FAMILY}`;\n\n const titleHeight = 25;\n const portSpacing = 14;\n const portVerticalGap = 5;\n const portsHeight = maxPorts > 0 ? (maxPorts - 1) * portSpacing + 20 + portVerticalGap : 0;\n\n let propertiesHeight = 0;\n if (nodeType.properties) {\n const visibleProps = nodeType.properties.filter(prop => {\n if (prop.type === 'array') return false;\n if (prop.visible === undefined) return true;\n return typeof prop.visible === 'function' ? prop.visible(node.properties) : prop.visible;\n });\n propertiesHeight = visibleProps.length * 16;\n }\n\n ctx.font = `500 12px ${FONT_FAMILY}`;\n\n const maxInputWidth = nodeType.inputs.reduce((max, input) =>\n Math.max(max, ctx.measureText(input.name).width), 0);\n\n const maxOutputWidth = nodeType.outputs.reduce((max, output) =>\n Math.max(max, ctx.measureText(output.name).width), 0);\n\n const portPadding = 40;\n const centerPadding = 40;\n const inputSection = maxInputWidth + portPadding;\n const outputSection = maxOutputWidth + portPadding;\n\n const width = inputSection + outputSection + centerPadding;\n const height = titleHeight +\n (maxPorts > 0 ? portsHeight : 0) +\n (propertiesHeight > 0 ? propertiesHeight + 10 : 0) +\n 5;\n\n return {\n width,\n height,\n portStartY: titleHeight,\n maxInputWidth,\n maxOutputWidth,\n inputSection,\n outputSection\n };\n }\n\n wrapText(ctx, text, maxWidth) {\n const words = text.split(' ');\n const lines = [];\n let currentLine = words[0];\n\n for (let i = 1; i < words.length; i++) {\n const word = words[i];\n const width = ctx.measureText(currentLine + \" \" + word).width;\n if (width < maxWidth) {\n currentLine += \" \" + word;\n } else {\n lines.push(currentLine);\n currentLine = word;\n }\n }\n lines.push(currentLine);\n return lines;\n }\n\n drawCanvas(ctx, nodes, edges, connecting, mousePosition, selectedNodes) {\n ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n ctx.save();\n this.camera.applyToContext(ctx);\n this.drawGrid(ctx, ctx.canvas.width, ctx.canvas.height);\n this.drawEdges(ctx, edges, nodes);\n this.drawNodes(ctx, nodes, edges, selectedNodes);\n if (connecting) {\n this.drawConnectionLine(ctx, connecting, mousePosition, nodes);\n }\n ctx.restore();\n }\n\n drawEdges(ctx, edges, nodes) {\n edges.forEach(edge => {\n const startNode = nodes.find(n => n.id === edge.start.nodeId);\n const endNode = nodes.find(n => n.id === edge.end.nodeId);\n if (!startNode || !endNode) return;\n\n const startDims = this.getNodeDimensions(startNode, ctx);\n const endDims = this.getNodeDimensions(endNode, ctx);\n\n if (!this.isRectInView(startNode.x, startNode.y, startDims.width, startDims.height, ctx.canvas.width, ctx.canvas.height, 200) &&\n !this.isRectInView(endNode.x, endNode.y, endDims.width, endDims.height, ctx.canvas.width, ctx.canvas.height, 200)) {\n return;\n }\n\n const getPortY = (node, dims, index) => {\n const titleHeight = 25;\n const portSpacing = 14;\n const portVerticalGap = 5;\n return node.y + titleHeight + portVerticalGap + (index * portSpacing) + 4;\n };\n\n const startPort = {\n x: edge.start.isInput ? startNode.x : startNode.x + startDims.width,\n y: getPortY(startNode, startDims, edge.start.index)\n };\n\n const endPort = {\n x: edge.end.isInput ? endNode.x : endNode.x + endDims.width,\n y: getPortY(endNode, endDims, edge.end.index)\n };\n\n const dx = endPort.x - startPort.x;\n const controlPoint1 = { x: startPort.x + dx * 0.5, y: startPort.y };\n const controlPoint2 = { x: endPort.x - dx * 0.5, y: endPort.y };\n\n // Draw the connection line\n ctx.beginPath();\n ctx.moveTo(startPort.x, startPort.y);\n ctx.bezierCurveTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPort.x, endPort.y);\n ctx.strokeStyle = '#666';\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Get the port types\n const startPortType = edge.start.isInput\n ? nodeTypes[startNode.type].inputs[edge.start.index].type\n : nodeTypes[startNode.type].outputs[edge.start.index].type;\n\n const endPortType = edge.end.isInput\n ? nodeTypes[endNode.type].inputs[edge.end.index].type\n : nodeTypes[endNode.type].outputs[edge.end.index].type;\n\n // Draw arrow if either port is a control type or if it's a data connection\n if (startPortType === 'control' || endPortType === 'control' || startPortType === 'data' || endPortType === 'data') {\n this.drawArrow(ctx, endPort.x, endPort.y, Math.atan2(endPort.y - controlPoint2.y, endPort.x - controlPoint2.x));\n }\n });\n }\n\n drawArrow(ctx, x, y, angle, size = 10) {\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(angle);\n ctx.beginPath();\n ctx.moveTo(0, 0);\n ctx.lineTo(-size, -size / 2);\n ctx.lineTo(-size, size / 2);\n ctx.closePath();\n ctx.fillStyle = '#666';\n ctx.fill();\n ctx.restore();\n }\n\n drawPortIcon(ctx, x, y, isInput) {\n const offset = 5; // Distance from node border\n const arrowX = isInput ? x - offset : x + offset;\n const portY = y;\n\n ctx.beginPath();\n if (isInput) {\n ctx.moveTo(arrowX - 6, portY - 5);\n ctx.lineTo(arrowX - 6, portY + 5);\n ctx.lineTo(arrowX, portY);\n } else {\n ctx.moveTo(arrowX, portY - 5);\n ctx.lineTo(arrowX, portY + 5);\n ctx.lineTo(arrowX + 6, portY);\n }\n ctx.closePath();\n ctx.strokeStyle = '#999999';\n ctx.lineWidth = 1;\n ctx.stroke();\n }\n\n drawLabelArrow(ctx, x, y, isControl) {\n if (isControl) {\n const lineLength = 10;\n\n // Draw line\n ctx.beginPath();\n ctx.moveTo(x, y);\n ctx.lineTo(x + lineLength, y);\n ctx.strokeStyle = '#4CAF50';\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Draw arrow\n ctx.beginPath();\n ctx.moveTo(x + lineLength, y - 5);\n ctx.lineTo(x + lineLength, y + 5);\n ctx.lineTo(x + lineLength + 6, y);\n ctx.closePath();\n ctx.fillStyle = '#4CAF50';\n ctx.fill();\n } else {\n // Draw orange circle for data ports\n ctx.beginPath();\n ctx.arc(x + 5, y, 4, 0, Math.PI * 2);\n ctx.fillStyle = '#FFA500';\n ctx.fill();\n }\n }\n\n drawNodes(ctx, nodes, edges, selectedNodes) {\n nodes.forEach(node => {\n const dimensions = this.getNodeDimensions(node, ctx);\n if (!this.isRectInView(node.x, node.y, dimensions.width, dimensions.height, ctx.canvas.width, ctx.canvas.height)) {\n return;\n }\n\n const nodeType = nodeTypes[node.type];\n this.drawNodeBody(ctx, node, dimensions, selectedNodes.includes(node));\n this.drawNodeContent(ctx, node, dimensions, nodeType, edges);\n this.drawNodeLabel(ctx, node, dimensions);\n });\n }\n\n drawNodeBody(ctx, node, dimensions, isSelected) {\n const nodeType = nodeTypes[node.type];\n ctx.fillStyle = nodeType.color;\n ctx.strokeStyle = isSelected ? '#FFFF00' : '#000000';\n ctx.lineWidth = 2;\n\n if (this.isNodeRoundingEnabled) {\n this.drawRoundedRect(ctx, node.x, node.y, dimensions.width, dimensions.height, 10);\n } else {\n ctx.fillRect(node.x, node.y, dimensions.width, dimensions.height);\n ctx.strokeRect(node.x, node.y, dimensions.width, dimensions.height);\n }\n }\n\n drawRoundedRect(ctx, x, y, width, height, radius) {\n ctx.beginPath();\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + width - radius, y);\n ctx.quadraticCurveTo(x + width, y, x + width, y + radius);\n ctx.lineTo(x + width, y + height - radius);\n ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);\n ctx.lineTo(x + radius, y + height);\n ctx.quadraticCurveTo(x, y + height, x, y + height - radius);\n ctx.lineTo(x, y + radius);\n ctx.quadraticCurveTo(x, y, x + radius, y);\n ctx.closePath();\n ctx.fill();\n ctx.stroke();\n }\n\n drawNodeContent(ctx, node, dimensions, nodeType, edges) {\n let currentHeight = 20;\n\n // Draw title with icon\n this.drawNodeTitle(ctx, node, currentHeight);\n currentHeight = this.drawNodeDescription(ctx, node, nodeType, dimensions, currentHeight);\n currentHeight = this.drawNodePorts(ctx, node, nodeType, dimensions, edges, currentHeight);\n this.drawNodeProperties(ctx, node, nodeType, currentHeight);\n }\n\n drawNodeTitle(ctx, node, yPosition) {\n ctx.fillStyle = 'white';\n ctx.font = `900 14px \"Font Awesome 5 Free\"`;\n ctx.fillText(this.getIconUnicode(getIconForNodeType(node.type)), node.x + 10, node.y + yPosition);\n\n ctx.font = `600 14px ${FONT_FAMILY}`;\n ctx.fillText(node.type, node.x + 30, node.y + yPosition);\n }\n\n drawNodeDescription(ctx, node, nodeType, dimensions, startHeight) {\n let currentHeight = startHeight;\n if (this.renderDescription) {\n ctx.font = `400 13px ${FONT_FAMILY}`;\n const descriptionLines = this.wrapText(ctx, nodeType.description, dimensions.width - 20);\n descriptionLines.forEach(line => {\n currentHeight += 14;\n ctx.fillText(line, node.x + 10, node.y + currentHeight + 3);\n });\n currentHeight += 15;\n } else {\n currentHeight += 15;\n }\n return currentHeight;\n }\n\n drawNodePorts(ctx, node, nodeType, dimensions, edges, startHeight) {\n let currentHeight = startHeight;\n\n nodeType.inputs.forEach((input, i) => {\n const portY = node.y + currentHeight + (i * 14);\n const isConnected = edges.some(edge =>\n edge.end.nodeId === node.id &&\n edge.end.index === i &&\n edge.end.isInput\n );\n\n if (!isConnected) {\n this.drawPortIcon(ctx, node.x, portY, true);\n }\n\n this.drawLabelArrow(ctx, node.x + 15, portY, input.type === 'control');\n ctx.fillStyle = 'white';\n ctx.fillText(input.name, node.x + 35, portY + 4);\n });\n\n nodeType.outputs.forEach((output, i) => {\n const portY = node.y + currentHeight + (i * 14);\n const isConnected = edges.some(edge =>\n edge.start.nodeId === node.id &&\n edge.start.index === i &&\n !edge.start.isInput\n );\n\n if (!isConnected) {\n this.drawPortIcon(ctx, node.x + dimensions.width, portY, false);\n }\n\n ctx.fillStyle = 'white';\n const textWidth = ctx.measureText(output.name).width;\n ctx.fillText(output.name, node.x + dimensions.width - textWidth - 35, portY + 4);\n this.drawLabelArrow(ctx, node.x + dimensions.width - 25, portY, output.type === 'control');\n });\n\n return currentHeight + Math.max(nodeType.inputs.length, nodeType.outputs.length) * 14 +\n (nodeType.inputs.length > 0 && nodeType.outputs.length > 0 ? 5 : 0);\n }\n\n drawNodeProperties(ctx, node, nodeType, startHeight) {\n if (!nodeType.properties) return;\n\n ctx.fillStyle = 'white';\n ctx.font = `500 12px ${FONT_FAMILY}`;\n let currentHeight = startHeight;\n\n nodeType.properties.forEach(prop => {\n if (prop.type === 'array') return;\n\n const isVisible = prop.visible === undefined ||\n (typeof prop.visible === 'function' ? prop.visible(node.properties) : prop.visible);\n\n if (isVisible) {\n const displayValue = node.properties[prop.name] !== undefined ? node.properties[prop.name] : prop.default;\n if (typeof displayValue === 'object') return;\n\n currentHeight += 16;\n ctx.fillText(`${prop.name}: ${displayValue}`, node.x + 10, node.y + currentHeight);\n }\n });\n }\n\n drawNodeLabel(ctx, node, dimensions) {\n if (!node.label) return;\n\n ctx.fillStyle = 'white';\n ctx.font = `400 12px ${FONT_FAMILY}`;\n const labelWidth = ctx.measureText(node.label).width;\n\n // Draw label centered below the node\n ctx.fillText(\n node.label,\n node.x + (dimensions.width - labelWidth) / 2,\n node.y + dimensions.height + 20\n );\n }\n\n drawConnectionLine(ctx, connecting, mousePosition, nodes) {\n if (!connecting) return;\n\n const node = nodes.find(n => n.id === connecting.nodeId);\n if (!node) return;\n\n const { width } = this.getNodeDimensions(node, ctx);\n const portY = node.y + this.getNodeDimensions(node, ctx).portStartY + (connecting.index * 14) + 8;\n\n // Calculate X position to start from middle of port\n const portOffset = 5;\n let portX;\n\n if (connecting.isInput) {\n if (nodeTypes[node.type].inputs[connecting.index].type === 'control') {\n // For control input ports: start from middle of triangle\n const triangleWidth = 6;\n portX = node.x - portOffset - (triangleWidth / 2);\n } else {\n // For data input ports: start from middle of circle\n portX = node.x - portOffset;\n }\n } else {\n if (nodeTypes[node.type].outputs[connecting.index].type === 'control') {\n // For control output ports: start from middle of triangle\n const triangleWidth = 6;\n portX = node.x + width + portOffset + (triangleWidth / 2);\n } else {\n // For data output ports: start from middle of circle\n portX = node.x + width + portOffset + 5; // +5 to match the circle x position in drawLabelArrow\n }\n }\n\n const startX = portX;\n const startY = portY;\n const endX = mousePosition.x;\n const endY = mousePosition.y;\n\n // Calculate control points for the Bezier curve\n const dx = endX - startX;\n const controlPoint1 = { x: startX + dx * 0.5, y: startY };\n const controlPoint2 = { x: endX - dx * 0.5, y: endY };\n\n ctx.beginPath();\n ctx.moveTo(startX, startY);\n ctx.bezierCurveTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endX, endY);\n ctx.strokeStyle = '#999999';\n ctx.lineWidth = 2;\n ctx.stroke();\n }\n\n setDarkTheme(isDarkTheme) {\n this.isDarkTheme = isDarkTheme;\n }\n\n setGridVisible(isGridVisible) {\n this.isGridVisible = isGridVisible;\n }\n\n setNodeRoundingEnabled(isNodeRoundingEnabled) {\n this.isNodeRoundingEnabled = isNodeRoundingEnabled;\n }\n\n isRectInView(x, y, width, height, canvasWidth, canvasHeight, padding = 0) {\n const { scale } = this.camera;\n const viewBounds = {\n left: -this.camera.x / scale,\n top: -this.camera.y / scale,\n right: (canvasWidth - this.camera.x) / scale,\n bottom: (canvasHeight - this.camera.y) / scale\n };\n\n return !(x + width < viewBounds.left - padding ||\n x > viewBounds.right + padding ||\n y + height < viewBounds.top - padding ||\n y > viewBounds.bottom + padding);\n }\n\n setRenderDescription(renderDescription) {\n this.renderDescription = renderDescription;\n }\n\n getIconUnicode(iconClass) {\n const iconMap = {\n 'fa-play': '\\uf04b',\n 'fa-terminal': '\\uf120',\n 'fa-cube': '\\uf1b2',\n 'fa-code': '\\uf121',\n 'fa-calculator': '\\uf1ec',\n 'fa-code-branch': '\\uf126',\n 'fa-sync': '\\uf021',\n 'fa-redo': '\\uf01e',\n 'fa-list': '\\uf03a',\n 'fa-globe': '\\uf0ac',\n 'fa-file-code': '\\uf1c9',\n 'fa-file-alt': '\\uf15c',\n 'fa-lock': '\\uf023',\n 'fa-unlock': '\\uf09c',\n 'fa-puzzle-piece': '\\uf12e'\n };\n return iconMap[iconClass] || iconMap['fa-puzzle-piece'];\n }\n}\n\nexport default Renderer;","class CodeGenerator {\r\n constructor(nodes, edges, settings) {\r\n this.nodes = nodes;\r\n this.edges = edges;\r\n this.settings = settings || {\r\n useStrict: true,\r\n useSemicolons: true,\r\n useConst: false,\r\n generateComments: true,\r\n };\r\n this.code = '';\r\n this.indentLevel = 0;\r\n this.hasAsyncOperations = false;\r\n this.variables = new Map();\r\n this.nodeOutputs = new Map();\r\n this.processedNodes = new Set();\r\n }\r\n\r\n generate() {\r\n this.code = '';\r\n if (this.settings.useStrict) {\r\n this.addLine('\"use strict\"');\r\n this.addLine();\r\n }\r\n this.generateImports();\r\n this.generateMainFunction();\r\n return this.code.trim(); // Trim to remove any trailing newlines\r\n }\r\n\r\n generateImports() {\r\n this.addLine(\"// Imports would go here\");\r\n this.addLine();\r\n }\r\n\r\n generateMainFunction() {\r\n this.hasAsyncOperations = this.nodes.some(node => node.type === 'HttpRequest' || node.type === 'WaitForSeconds');\r\n const asyncKeyword = this.hasAsyncOperations ? 'async ' : '';\r\n this.addLine(`${asyncKeyword}function main() {`);\r\n this.indentLevel++;\r\n\r\n const startNodes = this.nodes.filter(node => node.type === 'OnStart');\r\n startNodes.forEach(startNode => {\r\n this.processedNodes.clear();\r\n this.generateNodeCodeSequence(startNode);\r\n });\r\n\r\n this.indentLevel--;\r\n this.addLine('}');\r\n this.addLine();\r\n this.addLine('main()');\r\n }\r\n\r\n generateNodeCodeSequence(node) {\r\n if (this.processedNodes.has(node.id)) {\r\n return;\r\n }\r\n this.processedNodes.add(node.id);\r\n\r\n this.generateNodeCode(node);\r\n\r\n // Find and generate code for all connected nodes\r\n const connectedEdges = this.edges.filter(edge => edge.start.nodeId === node.id);\r\n connectedEdges.forEach(edge => {\r\n const nextNode = this.nodes.find(n => n.id === edge.end.nodeId);\r\n if (nextNode) {\r\n this.generateNodeCodeSequence(nextNode);\r\n }\r\n });\r\n }\r\n\r\n generateNodeCode(node) {\r\n if (this.settings.generateComments) {\r\n this.addLine(`// Processing ${node.type} node`);\r\n }\r\n\r\n switch (node.type) {\r\n case 'OnStart':\r\n // OnStart doesn't generate code, it's just a starting point\r\n break;\r\n case 'Log':\r\n this.handleLogNode(node);\r\n break;\r\n case 'Variable':\r\n this.handleVariableNode(node);\r\n break;\r\n case 'MathOperation':\r\n this.handleMathOperationNode(node);\r\n break;\r\n case 'Condition':\r\n this.addLine(`if (${node.properties.condition || 'true'}) {`);\r\n this.indentLevel++;\r\n // TODO: Generate code for true branch\r\n this.indentLevel--;\r\n this.addLine(\"} else {\");\r\n this.indentLevel++;\r\n // TODO: Generate code for false branch\r\n this.indentLevel--;\r\n this.addLine(\"}\");\r\n break;\r\n case 'WhileLoop':\r\n this.addLine(`while (${node.properties.condition || 'true'}) {`);\r\n this.indentLevel++;\r\n // TODO: Generate code for loop body\r\n this.indentLevel--;\r\n this.addLine(\"}\");\r\n break;\r\n case 'ForLoop':\r\n this.addLine(`for (let i = 0; i < ${node.properties.iterations || 10}; i++) {`);\r\n this.indentLevel++;\r\n // TODO: Generate code for loop body\r\n this.indentLevel--;\r\n this.addLine(\"}\");\r\n break;\r\n case 'HttpRequest':\r\n this.generateHttpRequestCode(node);\r\n break;\r\n case 'JSONParse':\r\n this.handleJSONParseNode(node);\r\n break;\r\n case 'JSONStringify':\r\n this.handleJSONStringifyNode(node);\r\n break;\r\n case 'Base64Encode':\r\n this.handleBase64EncodeNode(node);\r\n break;\r\n case 'Base64Decode':\r\n this.handleBase64DecodeNode(node);\r\n break;\r\n case 'If':\r\n this.handleIfNode(node);\r\n break;\r\n case 'Random':\r\n const randomType = node.properties.type || 'number';\r\n const resultVar = this.getUniqueVariableName('randomResult');\r\n\r\n switch (randomType) {\r\n case 'number':\r\n const min = parseFloat(node.properties.min) || 1;\r\n const max = parseFloat(node.properties.max) || 100;\r\n this.addLine(`const ${resultVar} = Math.floor(Math.random() * (${max} - ${min} + 1)) + ${min};`);\r\n break;\r\n\r\n case 'string':\r\n const length = parseInt(node.properties.length) || 10;\r\n const charset = node.properties.charset || 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\r\n this.addLine(`let ${resultVar} = '';`);\r\n this.addLine(`for (let i = 0; i < ${length}; i++) {`);\r\n this.indentLevel++;\r\n this.addLine(`${resultVar} += ${JSON.stringify(charset)}[Math.floor(Math.random() * ${charset.length})];`);\r\n this.indentLevel--;\r\n this.addLine(`}`);\r\n break;\r\n\r\n case 'boolean':\r\n const probability = parseFloat(node.properties.probability) || 50;\r\n this.addLine(`const ${resultVar} = Math.random() * 100 < ${probability};`);\r\n break;\r\n default:\r\n this.addLine(`// Unknown random type: ${node.properties.type}`);\r\n }\r\n\r\n this.nodeOutputs.set(node.id, resultVar);\r\n break;\r\n case 'Switch':\r\n const switchValue = this.getNodeInputValue(node, 1);\r\n if (switchValue === undefined) {\r\n this.addLine('// Warning: Switch node is missing input value');\r\n return;\r\n }\r\n\r\n const ignoreCase = node.properties.ignoreCase;\r\n const cases = node.properties.cases || [];\r\n\r\n if (ignoreCase) {\r\n this.addLine(`switch (${switchValue}.toString().toLowerCase()) {`);\r\n } else {\r\n this.addLine(`switch (${switchValue}) {`);\r\n }\r\n this.indentLevel++;\r\n\r\n // Generate case statements\r\n cases.forEach((caseObj, index) => {\r\n // Convert the case value based on its type\r\n let caseValue;\r\n switch (caseObj.type) {\r\n case 'number':\r\n caseValue = Number(caseObj.value);\r\n break;\r\n case 'boolean':\r\n caseValue = caseObj.value === 'true';\r\n break;\r\n default: // string\r\n caseValue = ignoreCase ? caseObj.value.toLowerCase() : caseObj.value;\r\n caseValue = JSON.stringify(caseValue);\r\n }\r\n\r\n this.addLine(`case ${caseValue}:`);\r\n this.indentLevel++;\r\n\r\n // Find and generate code for the case branch\r\n const caseEdge = this.edges.find(edge =>\r\n edge.start.nodeId === node.id &&\r\n edge.start.index === index + 1 && // +1 because index 0 is the default output\r\n !edge.start.isInput\r\n );\r\n\r\n if (caseEdge) {\r\n const nextNode = this.nodes.find(n => n.id === caseEdge.end.nodeId);\r\n if (nextNode) {\r\n this.generateNodeCodeSequence(nextNode);\r\n }\r\n }\r\n\r\n this.addLine('break;');\r\n this.indentLevel--;\r\n });\r\n\r\n // Default case\r\n this.addLine('default:');\r\n this.indentLevel++;\r\n\r\n // Find and generate code for the default branch\r\n const defaultEdge = this.edges.find(edge =>\r\n edge.start.nodeId === node.id &&\r\n edge.start.index === 0 &&\r\n !edge.start.isInput\r\n );\r\n\r\n if (defaultEdge) {\r\n const defaultNode = this.nodes.find(n => n.id === defaultEdge.end.nodeId);\r\n if (defaultNode) {\r\n this.generateNodeCodeSequence(defaultNode);\r\n }\r\n }\r\n\r\n this.addLine('break;');\r\n this.indentLevel--;\r\n\r\n this.indentLevel--;\r\n this.addLine('}');\r\n break;\r\n default:\r\n this.addLine(`// TODO: Implement ${node.type}`);\r\n }\r\n\r\n if (this.settings.generateComments) {\r\n this.addLine(`// Finished processing ${node.type} node`);\r\n this.addLine();\r\n }\r\n }\r\n\r\n findNextControlNode(node) {\r\n const controlEdge = this.edges.find(edge =>\r\n edge.start.nodeId === node.id &&\r\n edge.start.isInput === false &&\r\n edge.start.type === 'control'\r\n );\r\n if (controlEdge) {\r\n return this.nodes.find(n => n.id === controlEdge.end.nodeId);\r\n }\r\n return null;\r\n }\r\n\r\n handleLogNode(node) {\r\n const message = this.getNodeInputValue(node, 1);\r\n if (message !== undefined) {\r\n this.addLine(`console.${node.properties.logType || 'log'}(${message})`);\r\n } else if (node.properties.message) {\r\n this.addLine(`console.${node.properties.logType || 'log'}(${JSON.stringify(node.properties.message)})`);\r\n } else {\r\n this.addLine(`console.${node.properties.logType || 'log'}(\"Empty log message\")`);\r\n }\r\n }\r\n\r\n handleVariableNode(node) {\r\n const { name, type, initialValue } = node.properties;\r\n const inputValue = this.getNodeInputValue(node, 1);\r\n\r\n if (!this.variables.has(name)) {\r\n // First time this variable is used - declare and initialize it\r\n let declaration = `${this.settings.useConst ? 'const' : 'let'} ${name}`;\r\n if (inputValue !== undefined) {\r\n declaration += ` = ${inputValue}`;\r\n } else if (initialValue !== undefined && initialValue !== '') {\r\n declaration += ` = ${this.getTypedValue(type, initialValue)}`;\r\n }\r\n this.addLine(declaration);\r\n this.variables.set(name, type);\r\n } else if (inputValue !== undefined && inputValue !== name) {\r\n // Variable already declared, just update its value\r\n this.addLine(`${name} = ${inputValue}`);\r\n }\r\n\r\n this.nodeOutputs.set(node.id, name);\r\n }\r\n\r\n handleMathOperationNode(node) {\r\n const { operation } = node.properties;\r\n const inputA = this.getNodeInputValue(node, 1);\r\n const inputB = this.getNodeInputValue(node, 2);\r\n\r\n if (inputA !== undefined && inputB !== undefined) {\r\n const result = `(${inputA} ${operation} ${inputB})`;\r\n this.nodeOutputs.set(node.id, result);\r\n\r\n // Find the connected Variable node and update it\r\n const outputEdge = this.edges.find(edge => edge.start.nodeId === node.id && edge.start.index === 1);\r\n if (outputEdge) {\r\n const targetNode = this.nodes.find(n => n.id === outputEdge.end.nodeId);\r\n if (targetNode && targetNode.type === 'Variable') {\r\n this.addLine(`${targetNode.properties.name} = ${result};`);\r\n }\r\n }\r\n }\r\n }\r\n\r\n getNodeInputValue(node, inputIndex) {\r\n const inputEdge = this.edges.find(edge => edge.end.nodeId === node.id && edge.end.index === inputIndex);\r\n if (inputEdge) {\r\n const sourceNode = this.nodes.find(n => n.id === inputEdge.start.nodeId);\r\n if (sourceNode) {\r\n if (sourceNode.type === 'Variable') {\r\n return sourceNode.properties.name;\r\n }\r\n return this.nodeOutputs.get(sourceNode.id) || sourceNode.properties.initialValue;\r\n }\r\n }\r\n return undefined;\r\n }\r\n\r\n generateHttpRequestCode(node) {\r\n const { url, method, headers, body } = node.properties;\r\n let headersObj;\r\n try {\r\n headersObj = JSON.parse(headers);\r\n } catch (e) {\r\n headersObj = {};\r\n }\r\n\r\n this.addLine(`try {`);\r\n this.indentLevel++;\r\n this.addLine(`const response = await fetch(${JSON.stringify(url)}, {`);\r\n this.indentLevel++;\r\n this.addLine(`method: ${JSON.stringify(method)},`);\r\n this.addLine(`headers: ${JSON.stringify(headersObj)},`);\r\n if (method !== 'GET' && body) {\r\n this.addLine(`body: ${JSON.stringify(body)},`);\r\n }\r\n this.indentLevel--;\r\n this.addLine(`});`);\r\n\r\n this.addLine(`const responseBody = await response.text();`);\r\n this.addLine(`const statusCode = response.status;`);\r\n this.addLine(`const responseHeaders = Object.fromEntries(response.headers.entries());`);\r\n\r\n // Store the outputs\r\n this.nodeOutputs.set(node.id, {\r\n 'Response Body': 'responseBody',\r\n 'Status Code': 'statusCode',\r\n 'Headers': 'responseHeaders'\r\n });\r\n\r\n this.addLine(`console.log('Response body:', responseBody);`);\r\n this.addLine(`console.log('Status code:', statusCode);`);\r\n this.addLine(`console.log('Response headers:', responseHeaders);`);\r\n\r\n this.indentLevel--;\r\n this.addLine(`} catch (error) {`);\r\n this.indentLevel++;\r\n this.addLine(`console.error('HTTP Request failed:', error);`);\r\n this.indentLevel--;\r\n this.addLine(`}`);\r\n }\r\n\r\n async executeHttpRequest(node, debug) {\r\n const { url, method, headers, body } = node.properties;\r\n let headersObj;\r\n try {\r\n headersObj = JSON.parse(headers);\r\n } catch (e) {\r\n headersObj = {};\r\n }\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method,\r\n headers: headersObj,\r\n body: method !== 'GET' ? body : undefined\r\n });\r\n const responseBody = await response.text();\r\n const statusCode = response.status;\r\n const responseHeaders = Object.fromEntries(response.headers.entries());\r\n\r\n if (debug) {\r\n console.log('HTTP Request successful');\r\n console.log('Response body:', responseBody);\r\n console.log('Status code:', statusCode);\r\n console.log('Response headers:', responseHeaders);\r\n }\r\n\r\n // Store the outputs\r\n this.nodeOutputs.set(node.id, {\r\n 'Response Body': responseBody,\r\n 'Status Code': statusCode,\r\n 'Headers': responseHeaders\r\n });\r\n\r\n } catch (error) {\r\n if (debug) {\r\n console.error('HTTP Request failed');\r\n console.error('Error:', error);\r\n }\r\n }\r\n }\r\n\r\n getTypedValue(type, value) {\r\n switch (type) {\r\n case 'string':\r\n return `\"${value}\"`;\r\n case 'number':\r\n return Number(value);\r\n case 'boolean':\r\n return value === 'true';\r\n default:\r\n return value;\r\n }\r\n }\r\n\r\n getVariableOrLiteral(value) {\r\n if (this.variables.has(value)) {\r\n return value;\r\n }\r\n return JSON.stringify(value);\r\n }\r\n\r\n addLine(line = '') {\r\n if (line === '') {\r\n this.code += '\\n';\r\n } else {\r\n const indentation = ' '.repeat(this.indentLevel);\r\n const semicolon = this.settings.useSemicolons && this.shouldAddSemicolon(line) ? ';' : '';\r\n const cleanLine = line.replace(/;$/, '');\r\n this.code += `${indentation}${cleanLine}${semicolon}\\n`;\r\n }\r\n }\r\n\r\n executeNode(node, debug = false) {\r\n if (debug) {\r\n console.log(`Executing node: ${node.type} (ID: ${node.id})`);\r\n }\r\n\r\n switch (node.type) {\r\n case 'OnStart':\r\n if (debug) console.log('OnStart node triggered');\r\n break;\r\n case 'OnUpdate':\r\n if (debug) console.log('OnUpdate node triggered');\r\n break;\r\n case 'OnKeyboardInput':\r\n if (debug) console.log('OnKeyboardInput node triggered');\r\n break;\r\n case 'OnMouseDown':\r\n if (debug) console.log('OnMouseDown node triggered');\r\n break;\r\n case 'WhileLoop':\r\n if (debug) console.log('Executing WhileLoop');\r\n break;\r\n case 'ForLoop':\r\n if (debug) console.log('Executing ForLoop');\r\n break;\r\n case 'WaitForSeconds':\r\n if (debug) console.log(`Waiting for ${node.properties.delay || 1} seconds`);\r\n break;\r\n case 'Log':\r\n console.log(node.properties.message);\r\n break;\r\n case 'MathOperation':\r\n if (debug) console.log('Performing MathOperation');\r\n break;\r\n case 'Condition':\r\n if (debug) console.log(`Evaluating condition: ${node.properties.condition || 'true'}`);\r\n break;\r\n case 'Variable':\r\n if (debug) console.log('Processing Variable node');\r\n break;\r\n case 'Function':\r\n if (debug) console.log('Executing Function node');\r\n break;\r\n case 'GetComponent':\r\n if (debug) console.log('Getting component');\r\n break;\r\n case 'SetPosition':\r\n if (debug) console.log('Setting position');\r\n break;\r\n case 'GetInput':\r\n if (debug) console.log('Getting input');\r\n break;\r\n case 'HttpRequest':\r\n if (debug) console.log('Sending HTTP request');\r\n this.executeHttpRequest(node, debug);\r\n break;\r\n case 'JSONParse':\r\n if (debug) console.log('Parsing JSON');\r\n this.executeJSONParse(node, debug);\r\n break;\r\n case 'JSONStringify':\r\n if (debug) console.log('Stringifying JSON');\r\n this.executeJSONStringify(node, debug);\r\n break;\r\n case 'Base64Encode':\r\n if (debug) console.log('Encoding to Base64');\r\n this.executeBase64Encode(node, debug);\r\n break;\r\n case 'Base64Decode':\r\n if (debug) console.log('Decoding from Base64');\r\n this.executeBase64Decode(node, debug);\r\n break;\r\n default:\r\n console.warn(`Unknown node type: ${node.type}`);\r\n }\r\n\r\n // Find and execute connected nodes\r\n const connectedEdges = this.edges.filter(edge => edge.start.nodeId === node.id);\r\n connectedEdges.forEach(edge => {\r\n const nextNode = this.nodes.find(n => n.id === edge.end.nodeId);\r\n if (nextNode) {\r\n this.executeNode(nextNode, debug);\r\n }\r\n });\r\n }\r\n\r\n runScript(debug = false) {\r\n if (debug) {\r\n console.log('--- Starting script execution (debug mode) ---');\r\n }\r\n const startNodes = this.nodes.filter(node => node.type === 'OnStart');\r\n startNodes.forEach(startNode => this.executeNode(startNode, debug));\r\n if (debug) {\r\n console.log('--- Script execution completed ---');\r\n }\r\n }\r\n\r\n handleJSONParseNode(node) {\r\n const input = this.getNodeInputValue(node, 1);\r\n if (input !== undefined) {\r\n const outputVar = this.getUniqueVariableName('parsedJSON');\r\n this.addLine(`let ${outputVar};`);\r\n this.addLine(`try {`);\r\n this.indentLevel++;\r\n this.addLine(`${outputVar} = JSON.parse(${input});`);\r\n this.indentLevel--;\r\n this.addLine(`} catch (error) {`);\r\n this.indentLevel++;\r\n this.addLine(`console.error('JSON Parse Error:', error);`);\r\n this.addLine(`${outputVar} = null;`);\r\n this.indentLevel--;\r\n this.addLine(`}`);\r\n this.nodeOutputs.set(node.id, outputVar);\r\n }\r\n }\r\n\r\n handleJSONStringifyNode(node) {\r\n const input = this.getNodeInputValue(node, 1);\r\n const space = node.properties.space || 0;\r\n if (input !== undefined) {\r\n const outputVar = this.getUniqueVariableName('stringifiedJSON');\r\n this.addLine(`let ${outputVar};`);\r\n this.addLine(`try {`);\r\n this.indentLevel++;\r\n this.addLine(`${outputVar} = JSON.stringify(${input}, null, ${space});`);\r\n this.indentLevel--;\r\n this.addLine(`} catch (error) {`);\r\n this.indentLevel++;\r\n this.addLine(`console.error('JSON Stringify Error:', error);`);\r\n this.addLine(`${outputVar} = '';`);\r\n this.indentLevel--;\r\n this.addLine(`}`);\r\n this.nodeOutputs.set(node.id, outputVar);\r\n }\r\n }\r\n\r\n handleBase64EncodeNode(node) {\r\n const input = this.getNodeInputValue(node, 1);\r\n if (input !== undefined) {\r\n const outputVar = this.getUniqueVariableName('base64Encoded');\r\n this.addLine(`let ${outputVar};`);\r\n this.addLine(`try {`);\r\n this.indentLevel++;\r\n this.addLine(`${outputVar} = btoa(${input});`);\r\n this.indentLevel--;\r\n this.addLine(`} catch (error) {`);\r\n this.indentLevel++;\r\n this.addLine(`console.error('Base64 Encode Error:', error);`);\r\n this.addLine(`${outputVar} = '';`);\r\n this.indentLevel--;\r\n this.addLine(`}`);\r\n this.nodeOutputs.set(node.id, outputVar);\r\n }\r\n }\r\n\r\n handleBase64DecodeNode(node) {\r\n const input = this.getNodeInputValue(node, 1);\r\n if (input !== undefined) {\r\n const outputVar = this.getUniqueVariableName('base64Decoded');\r\n this.addLine(`let ${outputVar};`);\r\n this.addLine(`try {`);\r\n this.indentLevel++;\r\n this.addLine(`${outputVar} = atob(${input});`);\r\n this.indentLevel--;\r\n this.addLine(`} catch (error) {`);\r\n this.indentLevel++;\r\n this.addLine(`console.error('Base64 Decode Error:', error);`);\r\n this.addLine(`${outputVar} = '';`);\r\n this.indentLevel--;\r\n this.addLine(`}`);\r\n this.nodeOutputs.set(node.id, outputVar);\r\n }\r\n }\r\n\r\n getUniqueVariableName(baseName) {\r\n let counter = 1;\r\n let name = baseName;\r\n while (this.variables.has(name)) {\r\n name = `${baseName}${counter}`;\r\n counter++;\r\n }\r\n this.variables.set(name, 'any');\r\n return name;\r\n }\r\n\r\n executeJSONParse(node, debug) {\r\n const input = this.getNodeInputValue(node, 1);\r\n if (input !== undefined) {\r\n try {\r\n const result = JSON.parse(input);\r\n this.nodeOutputs.set(node.id, result);\r\n if (debug) console.log('JSON Parse result:', result);\r\n } catch (error) {\r\n if (debug) console.error('JSON Parse Error:', error);\r\n this.nodeOutputs.set(node.id, null);\r\n }\r\n }\r\n }\r\n\r\n executeJSONStringify(node, debug) {\r\n const input = this.getNodeInputValue(node, 1);\r\n const space = node.properties.space || 0;\r\n if (input !== undefined) {\r\n try {\r\n const result = JSON.stringify(input, null, space);\r\n this.nodeOutputs.set(node.id, result);\r\n if (debug) console.log('JSON Stringify result:', result);\r\n } catch (error) {\r\n if (debug) console.error('JSON Stringify Error:', error);\r\n this.nodeOutputs.set(node.id, '');\r\n }\r\n }\r\n }\r\n\r\n executeBase64Encode(node, debug) {\r\n const input = this.getNodeInputValue(node, 1);\r\n if (input !== undefined) {\r\n try {\r\n const result = btoa(input);\r\n this.nodeOutputs.set(node.id, result);\r\n if (debug) console.log('Base64 Encode result:', result);\r\n } catch (error) {\r\n if (debug) console.error('Base64 Encode Error:', error);\r\n this.nodeOutputs.set(node.id, '');\r\n }\r\n }\r\n }\r\n\r\n executeBase64Decode(node, debug) {\r\n const input = this.getNodeInputValue(node, 1);\r\n if (input !== undefined) {\r\n try {\r\n const result = atob(input);\r\n this.nodeOutputs.set(node.id, result);\r\n if (debug) console.log('Base64 Decode result:', result);\r\n } catch (error) {\r\n if (debug) console.error('Base64 Decode Error:', error);\r\n this.nodeOutputs.set(node.id, '');\r\n }\r\n }\r\n }\r\n\r\n shouldAddSemicolon(line) {\r\n // Don't add semicolons after these statements\r\n const noSemicolonPatterns = [\r\n /^function/,\r\n /^class/,\r\n /^if/,\r\n /^for/,\r\n /^while/,\r\n /^switch/,\r\n /^try/,\r\n /^catch/,\r\n /^finally/,\r\n /^case\\s/,\r\n /^default:/,\r\n /\\{$/,\r\n /\\}$/,\r\n /\\};$/,\r\n /;$/,\r\n // Also don't add semicolons after comments\r\n /^\\/\\//,\r\n /^\\/\\*/\r\n ];\r\n return !noSemicolonPatterns.some(pattern => pattern.test(line.trim()));\r\n }\r\n\r\n handleIfNode(node) {\r\n const inputA = this.getNodeInputValue(node, 1);\r\n const inputB = this.getNodeInputValue(node, 2);\r\n const operator = node.properties.operator || '==';\r\n\r\n if (inputA === undefined || inputB === undefined) {\r\n this.addLine('// Warning: If node is missing input values');\r\n return;\r\n }\r\n\r\n this.addLine(`if (${inputA} ${operator} ${inputB}) {`);\r\n this.indentLevel++;\r\n\r\n // Find and generate code for the 'True' branch\r\n const trueEdge = this.edges.find(edge =>\r\n edge.start.nodeId === node.id &&\r\n edge.start.isInput === false &&\r\n edge.start.index === 0\r\n );\r\n if (trueEdge) {\r\n const trueNode = this.nodes.find(n => n.id === trueEdge.end.nodeId);\r\n if (trueNode) {\r\n this.generateNodeCodeSequence(trueNode);\r\n }\r\n }\r\n\r\n this.indentLevel--;\r\n this.addLine(\"} else {\");\r\n this.indentLevel++;\r\n\r\n // Find and generate code for the 'False' branch\r\n const falseEdge = this.edges.find(edge =>\r\n edge.start.nodeId === node.id &&\r\n edge.start.isInput === false &&\r\n edge.start.index === 1\r\n );\r\n if (falseEdge) {\r\n const falseNode = this.nodes.find(n => n.id === falseEdge.end.nodeId);\r\n if (falseNode) {\r\n this.generateNodeCodeSequence(falseNode);\r\n }\r\n }\r\n\r\n this.indentLevel--;\r\n this.addLine(\"}\");\r\n }\r\n}\r\n\r\nexport default CodeGenerator;","const examples = {\n example1: {\n nodes: [\n { id: 1, type: 'OnStart', x: 100, y: 100, properties: {} },\n { id: 2, type: 'Log', x: 350, y: 100, properties: { message: 'Hello, World!', logType: 'log' } },\n ],\n edges: [\n { start: { nodeId: 1, isInput: false, index: 0 }, end: { nodeId: 2, isInput: true, index: 0 } },\n ]\n },\n example2: {\n nodes: [\n { id: 1, type: 'OnStart', x: 100, y: 100, properties: {} },\n { id: 2, type: 'Variable', x: 350, y: 50, properties: { name: 'a', type: 'number', initialValue: '5' } },\n { id: 3, type: 'Variable', x: 350, y: 200, properties: { name: 'b', type: 'number', initialValue: '3' } },\n { id: 4, type: 'MathOperation', x: 600, y: 125, properties: { operation: '+' } },\n { id: 5, type: 'Log', x: 850, y: 125, properties: { message: '', logType: 'log' } },\n ],\n edges: [\n { start: { nodeId: 1, isInput: false, index: 0 }, end: { nodeId: 2, isInput: true, index: 0 } },\n { start: { nodeId: 2, isInput: false, index: 0 }, end: { nodeId: 3, isInput: true, index: 0 } },\n { start: { nodeId: 2, isInput: false, index: 1 }, end: { nodeId: 4, isInput: true, index: 1 } },\n { start: { nodeId: 3, isInput: false, index: 1 }, end: { nodeId: 4, isInput: true, index: 2 } },\n { start: { nodeId: 3, isInput: false, index: 0 }, end: { nodeId: 4, isInput: true, index: 0 } },\n { start: { nodeId: 4, isInput: false, index: 0 }, end: { nodeId: 5, isInput: true, index: 0 } },\n { start: { nodeId: 4, isInput: false, index: 1 }, end: { nodeId: 5, isInput: true, index: 1 } },\n ]\n },\n example3: {\n nodes: [\n { id: 1, type: 'OnStart', x: 100, y: 100, properties: {} },\n { id: 2, type: 'Variable', x: 350, y: 50, properties: { name: 'a', type: 'number', initialValue: '5' } },\n { id: 3, type: 'Variable', x: 350, y: 200, properties: { name: 'b', type: 'number', initialValue: '3' } },\n { id: 4, type: 'If', x: 600, y: 100, properties: { operator: '==' } },\n { id: 5, type: 'Log', x: 850, y: 50, properties: { message: 'It is equal', logType: 'log' } },\n { id: 6, type: 'Log', x: 850, y: 200, properties: { message: 'It is not equal', logType: 'log' } },\n ],\n edges: [\n { start: { nodeId: 1, isInput: false, index: 0 }, end: { nodeId: 4, isInput: true, index: 0 } },\n { start: { nodeId: 2, isInput: false, index: 1 }, end: { nodeId: 4, isInput: true, index: 1 } },\n { start: { nodeId: 3, isInput: false, index: 1 }, end: { nodeId: 4, isInput: true, index: 2 } },\n { start: { nodeId: 4, isInput: false, index: 0 }, end: { nodeId: 5, isInput: true, index: 0 } },\n { start: { nodeId: 4, isInput: false, index: 1 }, end: { nodeId: 6, isInput: true, index: 0 } }\n ]\n },\n example4: {\n nodes: [\n { id: 1, type: 'OnStart', x: 100, y: 100, properties: {} },\n { id: 2, type: 'Random', x: 350, y: 100, properties: { min: '1', max: '100', type: 'number', probability: '50', charset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', length: '10' } },\n { id: 3, type: 'Variable', x: 600, y: 100, properties: { name: 'randomNum', type: 'number', initialValue: '0' } },\n { id: 4, type: 'Log', x: 850, y: 100, properties: { message: '', logType: 'log' } },\n ],\n edges: [\n { start: { nodeId: 1, isInput: false, index: 0 }, end: { nodeId: 2, isInput: true, index: 0 } },\n { start: { nodeId: 2, isInput: false, index: 0 }, end: { nodeId: 3, isInput: true, index: 0 } },\n { start: { nodeId: 3, isInput: false, index: 1 }, end: { nodeId: 4, isInput: true, index: 0 } },\n ]\n },\n example5: {\n nodes: [\n { id: 1, type: 'OnStart', x: 100, y: 100, properties: {} },\n { id: 2, type: 'Variable', x: 350, y: 100, properties: { name: 'a', type: 'number', initialValue: '5' } },\n {\n id: 4, type: 'Switch', x: 600, y: 100, properties: {\n cases: [\n { value: '5', output: 'Case 1' },\n { value: '3', output: 'Case 2' }\n ], ignoreCase: false\n }\n },\n { id: 5, type: 'Log', x: 850, y: 25, properties: { message: 'Value is 5', logType: 'log' } },\n { id: 6, type: 'Log', x: 850, y: 100, properties: { message: 'Value is 3', logType: 'log' } },\n { id: 7, type: 'Log', x: 850, y: 175, properties: { message: 'No match', logType: 'log' } }\n ],\n edges: [\n { start: { nodeId: 1, isInput: false, index: 0 }, end: { nodeId: 2, isInput: true, index: 0 } },\n { start: { nodeId: 2, isInput: false, index: 0 }, end: { nodeId: 4, isInput: true, index: 0 } },\n { start: { nodeId: 2, isInput: false, index: 1 }, end: { nodeId: 4, isInput: true, index: 1 } },\n { start: { nodeId: 4, isInput: false, index: 0 }, end: { nodeId: 5, isInput: true, index: 0 } },\n { start: { nodeId: 4, isInput: false, index: 1 }, end: { nodeId: 6, isInput: true, index: 0 } },\n { start: { nodeId: 4, isInput: false, index: 2 }, end: { nodeId: 7, isInput: true, index: 0 } }\n ]\n }\n};\n\nexport default examples;","// extracted by mini-css-extract-plugin\nexport default {\"container\":\"GraphInspector_container__jD7vj\",\"containerDark\":\"GraphInspector_containerDark__MLl2V\",\"containerLight\":\"GraphInspector_containerLight__5LZaC\",\"header\":\"GraphInspector_header__lKkcv\",\"headerDark\":\"GraphInspector_headerDark__2AGTU\",\"headerLight\":\"GraphInspector_headerLight__e4vZz\",\"headerContent\":\"GraphInspector_headerContent__mqJvC\",\"nodeIcon\":\"GraphInspector_nodeIcon__dG+Or\",\"nodeTitle\":\"GraphInspector_nodeTitle__IKc+b\",\"nodeTitleDark\":\"GraphInspector_nodeTitleDark__IFYbP\",\"nodeTitleLight\":\"GraphInspector_nodeTitleLight__cZWuY\",\"description\":\"GraphInspector_description__iPmhv\",\"descriptionDark\":\"GraphInspector_descriptionDark__ZhM63\",\"descriptionLight\":\"GraphInspector_descriptionLight__jgEJR\",\"sectionTitle\":\"GraphInspector_sectionTitle__wD0XT\",\"sectionTitleDark\":\"GraphInspector_sectionTitleDark__oGqGD\",\"sectionTitleLight\":\"GraphInspector_sectionTitleLight__m7KxZ\",\"propertyContainer\":\"GraphInspector_propertyContainer__MfLtO\",\"propertyLabel\":\"GraphInspector_propertyLabel__IM8WX\",\"propertyLabelDark\":\"GraphInspector_propertyLabelDark__NjIyF\",\"propertyLabelLight\":\"GraphInspector_propertyLabelLight__yq2M4\",\"input\":\"GraphInspector_input__4amzX\",\"inputDark\":\"GraphInspector_inputDark__h1KfY\",\"inputLight\":\"GraphInspector_inputLight__PI0DC\",\"portContainer\":\"GraphInspector_portContainer__-gEGz\",\"portContainerDark\":\"GraphInspector_portContainerDark__4WB5n\",\"portContainerLight\":\"GraphInspector_portContainerLight__M6r6f\",\"portIcon\":\"GraphInspector_portIcon__-MWbQ\",\"portIconControl\":\"GraphInspector_portIconControl__QVaWe\",\"portIconData\":\"GraphInspector_portIconData__FeE5l\",\"portType\":\"GraphInspector_portType__0T1ZM\",\"emptyMessage\":\"GraphInspector_emptyMessage__t1-T0\",\"emptyMessageDark\":\"GraphInspector_emptyMessageDark__Gmj4Y\",\"emptyMessageLight\":\"GraphInspector_emptyMessageLight__er4ci\",\"portIconControlInput\":\"GraphInspector_portIconControlInput__lzKCn GraphInspector_portIconControl__QVaWe\",\"portInfo\":\"GraphInspector_portInfo__VuRbn\",\"portNameRow\":\"GraphInspector_portNameRow__jGf7m\",\"portDescription\":\"GraphInspector_portDescription__CNpSR\",\"portDescriptionDark\":\"GraphInspector_portDescriptionDark__wxQiA\",\"portDescriptionLight\":\"GraphInspector_portDescriptionLight__babpV\",\"casesContainer\":\"GraphInspector_casesContainer__rj+6U\",\"caseRow\":\"GraphInspector_caseRow__NZw-n\",\"typeSelect\":\"GraphInspector_typeSelect__120y5\",\"addButton\":\"GraphInspector_addButton__Dmhqc\",\"removeButton\":\"GraphInspector_removeButton__1RYie\",\"addButtonDark\":\"GraphInspector_addButtonDark__yc7p9\",\"addButtonLight\":\"GraphInspector_addButtonLight__unnqe\",\"removeButtonDark\":\"GraphInspector_removeButtonDark__WWnFr\",\"removeButtonLight\":\"GraphInspector_removeButtonLight__KSGD2\",\"checkbox\":\"GraphInspector_checkbox__LX9Zl\",\"checkboxDark\":\"GraphInspector_checkboxDark__GRj05\",\"checkboxLight\":\"GraphInspector_checkboxLight__Lj1kl\",\"divider\":\"GraphInspector_divider__uB0oK\",\"dividerDark\":\"GraphInspector_dividerDark__jWnyS\",\"dividerLight\":\"GraphInspector_dividerLight__mBBXe\",\"parametersContainer\":\"GraphInspector_parametersContainer__nJhzD\",\"parameterRow\":\"GraphInspector_parameterRow__JWIs3\"};","import React from 'react';\nimport styles from './GraphInspector.module.css';\nimport { getIconForNodeType } from './ContextMenu';\n\nconst PortItem = ({ port, isDarkTheme }) => (\n \n
\n
\n
\n \"{port.name}\"\n ({port.type})\n
\n {port.description && (\n
\n {port.description}\n
\n )}\n
\n
\n);\n\nconst PropertyInput = ({ property, node, updateNodeProperty, isDarkTheme }) => {\n const renderArrayProperty = () => {\n if (property.name === 'parameters') {\n return renderParametersInput();\n }\n if (property.name === 'cases') {\n return renderCasesInput();\n }\n return null;\n };\n\n const renderParametersInput = () => (\n \n {(node.properties.parameters || []).map((param, index) => (\n
\n updateParameter(index, 'name', e.target.value)}\n placeholder=\"Parameter name\"\n className={`${styles.input} ${isDarkTheme ? styles.inputDark : styles.inputLight}`}\n />\n \n \n
\n ))}\n
\n
\n );\n\n const renderCasesInput = () => (\n \n {(node.properties.cases || []).map((caseObj, index) => (\n
\n \n {caseObj.type === 'boolean' ? (\n \n ) : (\n updateCase(index, 'value', e.target.value)}\n placeholder=\"Case value\"\n className={`${styles.input} ${isDarkTheme ? styles.inputDark : styles.inputLight}`}\n />\n )}\n \n
\n ))}\n
\n
\n );\n\n const updateParameter = (index, field, value) => {\n const newParams = [...node.properties.parameters];\n newParams[index] = { ...newParams[index], [field]: value };\n updateNodeProperty('parameters', newParams);\n };\n\n const removeParameter = (index) => {\n const newParams = node.properties.parameters.filter((_, i) => i !== index);\n updateNodeProperty('parameters', newParams);\n };\n\n const addParameter = () => {\n const newParams = [\n ...(node.properties.parameters || []),\n { name: `param${(node.properties.parameters || []).length + 1}`, type: 'string' }\n ];\n updateNodeProperty('parameters', newParams);\n };\n\n const updateCase = (index, field, value) => {\n const newCases = [...node.properties.cases];\n newCases[index] = {\n ...newCases[index],\n [field]: value,\n value: field === 'type' && value === 'number' ?\n (isNaN(newCases[index].value) ? '0' : newCases[index].value) :\n String(newCases[index].value)\n };\n updateNodeProperty('cases', newCases);\n };\n\n const removeCase = (index) => {\n const newCases = node.properties.cases.filter((_, i) => i !== index);\n updateNodeProperty('cases', newCases);\n };\n\n const addCase = () => {\n const newCases = [\n ...(node.properties.cases || []),\n {\n value: '',\n type: 'string',\n output: `Case ${(node.properties.cases || []).length + 1}`\n }\n ];\n updateNodeProperty('cases', newCases);\n };\n\n switch (property.type) {\n case 'select':\n return (\n \n );\n case 'boolean':\n return (\n updateNodeProperty(property.name, e.target.checked)}\n className={`${styles.checkbox} ${isDarkTheme ? styles.checkboxDark : styles.checkboxLight}`}\n />\n );\n case 'array':\n return renderArrayProperty();\n default:\n return (\n updateNodeProperty(property.name, e.target.value)}\n className={`${styles.input} ${isDarkTheme ? styles.inputDark : styles.inputLight}`}\n />\n );\n }\n};\n\nconst GraphInspector = ({ selectedNodes, nodeTypes, updateNodeProperty, config }) => {\n if (selectedNodes.length === 0) {\n return (\n \n No node selected\n
\n );\n }\n\n const node = selectedNodes[0];\n const nodeType = nodeTypes[node.type];\n\n return (\n \n
\n
\n \n {node.type}\n
\n
\n\n
\n
\n
Description
\n
{nodeType.description}
\n
\n\n
\n\n {nodeType.properties && nodeType.properties.length > 0 && (\n <>\n
\n
Properties
\n {nodeType.properties.map(prop => (\n prop.visible === undefined ||\n (typeof prop.visible === 'function' ? prop.visible(node.properties) : prop.visible)\n ) && (\n
\n ))}\n
\n
\n >\n )}\n\n
\n
Input Ports
\n {nodeType.inputs.map((input, index) => (\n
\n ))}\n\n
\n
Output Ports
\n {nodeType.outputs.map((output, index) => (\n
\n ))}\n
\n
\n );\n};\n\nexport default GraphInspector;","const PORT_CONSTANTS = {\r\n WIDTH: 6,\r\n HEIGHT: 10,\r\n OFFSET: 5,\r\n SCALE_MULTIPLIER: 1.5,\r\n VERTICAL_OFFSET: 2,\r\n BASE_Y: 35,\r\n SPACING: 14,\r\n TRIANGLE_WIDTH: 6,\r\n TRIANGLE_HEIGHT: 10\r\n};\r\n\r\nclass Node {\r\n constructor(id, type, x, y, nodeTypes) {\r\n this.id = id;\r\n this.type = type;\r\n this.x = x;\r\n this.y = y;\r\n this.properties = {};\r\n this.label = '';\r\n\r\n // Initialize properties with default values from nodeType\r\n const nodeType = nodeTypes[type];\r\n if (nodeType && nodeType.properties) {\r\n nodeType.properties.forEach(prop => {\r\n this.properties[prop.name] = prop.default;\r\n });\r\n }\r\n }\r\n\r\n static create(type, x, y, nodeTypes) {\r\n return new Node(Date.now(), type, x, y, nodeTypes);\r\n }\r\n\r\n static fromJSON(nodeData, nodeTypes) {\r\n const node = new Node(nodeData.id, nodeData.type, nodeData.x, nodeData.y, nodeTypes);\r\n node.properties = { ...nodeData.properties };\r\n node.label = nodeData.label || '';\r\n return node;\r\n }\r\n\r\n static createFromExample(exampleNode, nodeTypes) {\r\n // Special handling for Switch nodes with dynamic outputs\r\n if (exampleNode.type === 'Switch' && exampleNode.properties.cases) {\r\n Node.updateSwitchNodeType(exampleNode, nodeTypes);\r\n }\r\n return Node.fromJSON(exampleNode, nodeTypes);\r\n }\r\n\r\n static updateSwitchNodeType(node, nodeTypes) {\r\n const switchNode = nodeTypes['Switch'];\r\n const baseOutputs = [...switchNode.outputs];\r\n\r\n node.properties.cases.forEach((caseItem, index) => {\r\n baseOutputs.splice(index, 0, {\r\n type: 'control',\r\n name: caseItem.output,\r\n description: `Triggered when value matches ${caseItem.value}`\r\n });\r\n });\r\n\r\n nodeTypes['Switch'] = {\r\n ...switchNode,\r\n outputs: baseOutputs\r\n };\r\n }\r\n\r\n updateProperty(property, value) {\r\n this.properties[property] = value;\r\n return this;\r\n }\r\n\r\n clone(offset = { x: 20, y: 20 }) {\r\n const clonedNode = new Node(Date.now() + Math.random(), this.type, this.x + offset.x, this.y + offset.y);\r\n clonedNode.properties = { ...this.properties };\r\n return clonedNode;\r\n }\r\n\r\n isPointInside(x, y, dimensions) {\r\n return (\r\n x >= this.x &&\r\n x <= this.x + dimensions.width &&\r\n y >= this.y &&\r\n y <= this.y + dimensions.height\r\n );\r\n }\r\n\r\n getPortPosition(portIndex, isInput, dimensions) {\r\n const portY = this.y + PORT_CONSTANTS.BASE_Y + (portIndex * PORT_CONSTANTS.SPACING);\r\n const portYMiddle = portY + (PORT_CONSTANTS.TRIANGLE_HEIGHT / 2);\r\n\r\n const portX = isInput\r\n ? this.x - PORT_CONSTANTS.OFFSET - (PORT_CONSTANTS.TRIANGLE_WIDTH / 2)\r\n : this.x + dimensions.width + PORT_CONSTANTS.OFFSET + (PORT_CONSTANTS.TRIANGLE_WIDTH / 2);\r\n\r\n return {\r\n x: portX,\r\n y: portYMiddle,\r\n nodeId: this.id,\r\n isInput,\r\n index: portIndex\r\n };\r\n }\r\n\r\n isPortClicked(x, y, portIndex, isInput, dimensions) {\r\n const portX = isInput\r\n ? this.x - PORT_CONSTANTS.OFFSET - PORT_CONSTANTS.WIDTH\r\n : this.x + dimensions.width + PORT_CONSTANTS.OFFSET;\r\n\r\n const portY = this.y + PORT_CONSTANTS.BASE_Y + (portIndex * PORT_CONSTANTS.SPACING);\r\n\r\n return (\r\n x >= portX &&\r\n x <= portX + PORT_CONSTANTS.WIDTH * PORT_CONSTANTS.SCALE_MULTIPLIER &&\r\n y >= portY - PORT_CONSTANTS.VERTICAL_OFFSET &&\r\n y <= portY + PORT_CONSTANTS.HEIGHT + PORT_CONSTANTS.VERTICAL_OFFSET\r\n );\r\n }\r\n\r\n findClickedPort(x, y, dimensions, nodeType) {\r\n // Check input ports\r\n for (let i = 0; i < nodeType.inputs.length; i++) {\r\n if (this.isPortClicked(x, y, i, true, dimensions)) {\r\n return this.getPortPosition(i, true, dimensions);\r\n }\r\n }\r\n\r\n // Check output ports\r\n for (let i = 0; i < nodeType.outputs.length; i++) {\r\n if (this.isPortClicked(x, y, i, false, dimensions)) {\r\n return this.getPortPosition(i, false, dimensions);\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n move(dx, dy) {\r\n this.x += dx;\r\n this.y += dy;\r\n return this;\r\n }\r\n\r\n moveTo(x, y) {\r\n this.x = x;\r\n this.y = y;\r\n return this;\r\n }\r\n\r\n toJSON() {\r\n return {\r\n id: this.id,\r\n type: this.type,\r\n x: this.x,\r\n y: this.y,\r\n properties: { ...this.properties },\r\n label: this.label\r\n };\r\n }\r\n\r\n static createInstance(nodeData, nodeTypes) {\r\n return Node.fromJSON(nodeData, nodeTypes);\r\n }\r\n}\r\n\r\nexport default Node;","import React from 'react';\r\nimport styles from './ContextMenu.module.css';\r\n\r\nconst NodeContextMenu = ({ visible, x, y, camera, onAction }) => {\r\n if (!visible) return null;\r\n\r\n const menuItems = [\r\n { icon: 'fa-copy', label: 'Copy', action: 'copy' },\r\n { icon: 'fa-trash', label: 'Delete', action: 'delete' },\r\n { icon: 'fa-cut', label: 'Cut', action: 'cut' },\r\n { icon: 'fa-clone', label: 'Duplicate', action: 'duplicate' },\r\n { icon: 'fa-tag', label: 'Set Label', action: 'setLabel' },\r\n ];\r\n\r\n return (\r\n \r\n
\r\n {menuItems.map(({ icon, label, action }) => (\r\n \r\n ))}\r\n
\r\n
\r\n );\r\n};\r\n\r\nexport default NodeContextMenu;","import React, { useRef, useEffect, useState, useCallback } from 'react';\nimport MenuBar from './components/MenuBar';\nimport ContextMenu from './components/ContextMenu';\nimport Minimap from './components/Minimap';\nimport Tabs from './components/Tabs';\nimport SettingsTab from './components/SettingsTab';\nimport Camera from './engine/Camera';\nimport Renderer from './engine/Renderer';\nimport CodeGenerator from './CodeGenerator';\nimport { nodeTypes, nodeGroups } from './nodeDefinitions';\nimport examples from './examples';\nimport { saveAs } from 'file-saver';\nimport GraphInspector from './components/GraphInspector';\nimport Node from './engine/Node';\nimport NodeContextMenu from './components/NodeContextMenu';\n\nconst VisualScripting = () => {\n // #region State Declarations\n const canvasRef = useRef(null);\n const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 });\n const [nodes, setNodes] = useState(() => []);\n const [edges, setEdges] = useState([]);\n const [draggingNode, setDraggingNode] = useState(null);\n const [connecting, setConnecting] = useState(null);\n const [selectedNode, setSelectedNode] = useState(null); // eslint-disable-line no-unused-vars\n const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });\n const [canvasSize, setCanvasSize] = useState({ width: window.innerWidth, height: window.innerHeight });\n const [menuOpen, setMenuOpen] = useState(null);\n const [camera] = useState(new Camera(0, 0, 1, 0.75, 2));\n const [config, setConfig] = useState({\n isDarkTheme: true,\n isGridVisible: true,\n isMinimapVisible: false,\n isNodeRoundingEnabled: true,\n isGraphInspectorVisible: true,\n codeGenerator: {\n useStrict: true,\n useSemicolons: true,\n useConst: false,\n generateComments: true,\n }\n });\n const [needsRedraw, setNeedsRedraw] = useState(true);\n const [renderer] = useState(() => new Renderer(camera, config.isDarkTheme, config.isGridVisible, config.isNodeRoundingEnabled));\n const [isDraggingCanvas, setIsDraggingCanvas] = useState(false);\n const [lastMousePosition, setLastMousePosition] = useState({ x: 0, y: 0 });\n const [undoStack, setUndoStack] = useState([]);\n const [redoStack, setRedoStack] = useState([]);\n const [copiedNodes, setCopiedNodes] = useState([]);\n const [selectedNodes, setSelectedNodes] = useState([]);\n const [isMultiSelectMode, setIsMultiSelectMode] = useState(false);\n const [tabs, setTabs] = useState([{ id: 'untitled-1', title: 'Untitled-1', type: 'Export' }]);\n const [activeTab, setActiveTab] = useState('untitled-1');\n const [nodeContextMenu, setNodeContextMenu] = useState({ visible: false, x: 0, y: 0 });\n // #endregion\n\n // #region Drawing Functions\n const drawCanvas = useCallback(() => {\n if (!needsRedraw) return;\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n renderer.drawCanvas(ctx, nodes, edges, connecting, mousePosition, selectedNodes);\n setNeedsRedraw(false);\n }, [nodes, edges, connecting, mousePosition, selectedNodes, needsRedraw, renderer]);\n // #endregion\n\n // #region Event Handlers\n const handleContextMenu = (e) => {\n e.preventDefault();\n const rect = canvasRef.current.getBoundingClientRect();\n const { x, y } = camera.screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\n\n const clickedNode = findClickedNode(x, y);\n if (clickedNode) {\n setNodeContextMenu({ visible: true, x, y });\n if (!selectedNodes.includes(clickedNode)) {\n setSelectedNodes([clickedNode]);\n }\n } else {\n setContextMenu({ visible: true, x, y });\n }\n setNeedsRedraw(true);\n };\n\n const handleCanvasClick = (e) => {\n const rect = canvasRef.current.getBoundingClientRect();\n const { x, y } = camera.screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\n\n if (contextMenu.visible) {\n setContextMenu({ ...contextMenu, visible: false });\n }\n if (nodeContextMenu.visible) {\n setNodeContextMenu({ ...nodeContextMenu, visible: false });\n }\n\n if (connecting) {\n const clickedPort = findClickedPort(x, y);\n if (clickedPort && clickedPort.nodeId !== connecting.nodeId) {\n const newEdge = {\n start: connecting.isInput ? clickedPort : connecting,\n end: connecting.isInput ? connecting : clickedPort\n };\n setEdges([...edges, newEdge]);\n }\n setConnecting(null);\n } else {\n const clickedNode = findClickedNode(x, y);\n if (clickedNode) {\n if (isMultiSelectMode) {\n setSelectedNodes(prevSelected =>\n prevSelected.includes(clickedNode)\n ? prevSelected.filter(node => node !== clickedNode)\n : [...prevSelected, clickedNode]\n );\n } else {\n setSelectedNodes([clickedNode]);\n }\n } else {\n setSelectedNodes([]);\n }\n }\n setNeedsRedraw(true);\n };\n\n const handleMouseDown = (e) => {\n const rect = canvasRef.current.getBoundingClientRect();\n const { x, y } = camera.screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\n\n if (contextMenu.visible) {\n setContextMenu({ ...contextMenu, visible: false });\n }\n if (nodeContextMenu.visible) {\n setNodeContextMenu({ ...nodeContextMenu, visible: false });\n }\n\n const clickedPort = findClickedPort(x, y);\n if (clickedPort) {\n const node = nodes.find(n => n.id === clickedPort.nodeId);\n const dimensions = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));\n const portPosition = node.getPortPosition(clickedPort.index, clickedPort.isInput, dimensions);\n setConnecting(portPosition);\n } else {\n const clickedNode = findClickedNode(x, y);\n if (clickedNode) {\n setSelectedNode(clickedNode);\n setDraggingNode({ id: clickedNode.id, offsetX: x - clickedNode.x, offsetY: y - clickedNode.y });\n } else {\n setSelectedNode(null);\n setIsDraggingCanvas(true);\n setLastMousePosition({ x: e.clientX, y: e.clientY });\n }\n }\n };\n\n const handleMouseMove = (e) => {\n const rect = canvasRef.current.getBoundingClientRect();\n const { x, y } = camera.screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\n\n if (isDraggingCanvas) {\n const dx = e.clientX - lastMousePosition.x;\n const dy = e.clientY - lastMousePosition.y;\n camera.move(dx, dy);\n setLastMousePosition({ x: e.clientX, y: e.clientY });\n } else if (draggingNode) {\n setNodes(nodes.map(node => {\n if (node.id === draggingNode.id) {\n return Node.createInstance(node, nodeTypes)\n .moveTo(x - draggingNode.offsetX, y - draggingNode.offsetY);\n }\n return node;\n }));\n }\n\n setMousePosition({ x, y });\n setNeedsRedraw(true);\n };\n\n const handleMouseUp = (e) => {\n if (connecting) {\n const rect = canvasRef.current.getBoundingClientRect();\n const { x, y } = camera.screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\n const clickedPort = findClickedPort(x, y);\n if (clickedPort && clickedPort.nodeId !== connecting.nodeId) {\n const newEdge = {\n start: connecting.isInput ? clickedPort : connecting,\n end: connecting.isInput ? connecting : clickedPort\n };\n setEdges([...edges, newEdge]);\n }\n setConnecting(null);\n }\n\n if (isDraggingCanvas) {\n setIsDraggingCanvas(false);\n } else if (draggingNode) {\n const draggedNode = nodes.find(node => node.id === draggingNode.id);\n setSelectedNode(draggedNode);\n } else {\n const rect = canvasRef.current.getBoundingClientRect();\n const { x, y } = camera.screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\n const clickedNode = findClickedNode(x, y);\n if (clickedNode) {\n setSelectedNode(clickedNode);\n }\n }\n\n setDraggingNode(null);\n };\n\n const handleWheel = useCallback((e) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n e.preventDefault();\n const rect = canvas.getBoundingClientRect();\n const { x, y } = camera.screenToWorld(e.clientX - rect.left, e.clientY - rect.top);\n const factor = e.deltaY > 0 ? 0.9 : 1.1;\n camera.zoom(factor, x, y);\n setNeedsRedraw(true);\n }, [camera, drawCanvas]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleKeyDown = (e) => {\n if (e.key === 'Delete' && selectedNodes.length > 0) {\n deleteSelectedNodes();\n } else if (e.key === 'Control') {\n setIsMultiSelectMode(true);\n }\n };\n\n const handleKeyUp = (e) => {\n if (e.key === 'Control') {\n setIsMultiSelectMode(false);\n }\n };\n // #endregion\n\n // #region Node Operations\n const findClickedNode = (x, y) => {\n return nodes.find(node => {\n const nodeInstance = Node.createInstance(node, nodeTypes);\n return nodeInstance.isPointInside(x, y, renderer.getNodeDimensions(node, canvasRef.current.getContext('2d')));\n });\n };\n\n const findClickedPort = (x, y) => {\n for (const node of nodes) {\n const nodeType = nodeTypes[node.type];\n const dimensions = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));\n\n // Port icon dimensions (from Renderer.drawPortIcon)\n const portIconWidth = 6 * 1.5; // Base width of triangle * scale\n const portIconHeight = 10 * 1.5; // Base height of triangle * scale\n const portOffset = 5; // Distance from node border\n\n // Calculate port Y position using the same logic as in drawEdges\n const getPortY = (index) => {\n const titleHeight = 25;\n const portSpacing = 14;\n const portVerticalGap = 5;\n return node.y + titleHeight + portVerticalGap + (index * portSpacing) + 4;\n };\n\n // Check input ports\n for (let i = 0; i < nodeType.inputs.length; i++) {\n const portY = getPortY(i);\n const portX = node.x - portOffset;\n\n // Create a square hitbox around the port\n if (x >= portX - portIconWidth &&\n x <= portX + portIconWidth &&\n y >= portY - portIconHeight / 2 &&\n y <= portY + portIconHeight / 2) {\n return node.getPortPosition(i, true, dimensions);\n }\n }\n\n // Check output ports\n for (let i = 0; i < nodeType.outputs.length; i++) {\n const portY = getPortY(i);\n const portX = node.x + dimensions.width + portOffset;\n\n // Create a square hitbox around the port\n if (x >= portX - portIconWidth &&\n x <= portX + portIconWidth &&\n y >= portY - portIconHeight / 2 &&\n y <= portY + portIconHeight / 2) {\n return node.getPortPosition(i, false, dimensions);\n }\n }\n }\n return null;\n };\n\n const addNode = (type) => {\n const newNode = Node.create(type, contextMenu.x, contextMenu.y, nodeTypes);\n const newNodes = [...nodes, newNode];\n setUndoStack([...undoStack, { nodes, edges }]);\n setRedoStack([]);\n setNodes(newNodes);\n setContextMenu({ ...contextMenu, visible: false });\n setNeedsRedraw(true);\n };\n\n const updateNodeProperty = (property, value) => {\n const updatedNodes = nodes.map(node => {\n if (node.id === selectedNodes[0].id) {\n return Node.createInstance(node, nodeTypes)\n .updateProperty(property, value);\n }\n return node;\n });\n\n setUndoStack([...undoStack, { nodes, edges }]);\n setRedoStack([]);\n setNodes(updatedNodes);\n\n setSelectedNodes(prevSelected => prevSelected.map(node => {\n if (node.id === selectedNodes[0].id) {\n return Node.createInstance(node, nodeTypes)\n .updateProperty(property, value);\n }\n return node;\n }));\n\n setNeedsRedraw(true);\n };\n\n const deleteSelectedNodes = () => {\n if (selectedNodes.length > 0) {\n setUndoStack([...undoStack, { nodes, edges }]);\n setRedoStack([]);\n const selectedNodeIds = selectedNodes.map(node => node.id);\n setNodes(nodes.filter(node => !selectedNodeIds.includes(node.id)));\n setEdges(edges.filter(edge =>\n !selectedNodeIds.includes(edge.start.nodeId) && !selectedNodeIds.includes(edge.end.nodeId)\n ));\n setSelectedNodes([]);\n setNeedsRedraw(true);\n }\n };\n\n const setNodeLabel = () => {\n if (selectedNodes.length === 0) return;\n\n const currentNode = selectedNodes[0];\n const newLabel = prompt('Enter node label:', currentNode.label || '');\n\n if (newLabel !== null) { // Check if user didn't cancel\n setNodes(nodes.map(node => {\n if (node.id === currentNode.id) {\n const updatedNode = Node.createInstance(node, nodeTypes);\n updatedNode.label = newLabel;\n return updatedNode;\n }\n return node;\n }));\n\n setSelectedNodes(prevSelected => prevSelected.map(node => {\n if (node.id === currentNode.id) {\n const updatedNode = Node.createInstance(node, nodeTypes);\n updatedNode.label = newLabel;\n return updatedNode;\n }\n return node;\n }));\n\n setNeedsRedraw(true);\n }\n };\n // #endregion\n\n // #region Menu Operations\n const handleMenuClick = (menu) => {\n setMenuOpen(menuOpen === menu ? null : menu);\n };\n\n const handleMenuItemClick = (action, param) => {\n switch (action) {\n case 'new':\n if (window.confirm('Are you sure you want to create a new project? All unsaved changes will be lost.')) {\n setNodes([]);\n setEdges([]);\n setUndoStack([]);\n setRedoStack([]);\n }\n break;\n case 'open':\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = '.json';\n input.onchange = (e) => {\n const file = e.target.files[0];\n if (file) {\n const reader = new FileReader();\n reader.onload = (event) => {\n const content = JSON.parse(event.target.result);\n setNodes(content.nodes);\n setEdges(content.edges);\n setUndoStack([]);\n setRedoStack([]);\n };\n reader.readAsText(file);\n }\n };\n input.click();\n break;\n case 'loadExample':\n if (examples[param]) {\n const exampleNodes = examples[param].nodes.map(node =>\n Node.createFromExample(node, nodeTypes)\n );\n\n // Reconstruct edges with proper port positions\n const reconstructedEdges = examples[param].edges.map(edge => {\n const startNode = exampleNodes.find(n => n.id === edge.start.nodeId);\n const endNode = exampleNodes.find(n => n.id === edge.end.nodeId);\n\n if (startNode && endNode) {\n const startDimensions = renderer.getNodeDimensions(startNode, canvasRef.current.getContext('2d'));\n const endDimensions = renderer.getNodeDimensions(endNode, canvasRef.current.getContext('2d'));\n\n return {\n start: {\n ...edge.start,\n x: edge.start.isInput ? startNode.x : startNode.x + startDimensions.width,\n y: startNode.y + startDimensions.portStartY + (edge.start.index * 20)\n },\n end: {\n ...edge.end,\n x: edge.end.isInput ? endNode.x : endNode.x + endDimensions.width,\n y: endNode.y + endDimensions.portStartY + (edge.end.index * 20)\n }\n };\n }\n return null;\n }).filter(edge => edge !== null);\n\n setNodes(exampleNodes);\n setEdges(reconstructedEdges);\n setUndoStack([]);\n setRedoStack([]);\n }\n break;\n case 'save':\n const projectData = JSON.stringify({ nodes, edges });\n const blob = new Blob([projectData], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = 'visual_script.json';\n a.click();\n URL.revokeObjectURL(url);\n break;\n case 'undo':\n if (undoStack.length > 0) {\n const prevState = undoStack[undoStack.length - 1];\n setRedoStack([...redoStack, { nodes, edges }]);\n setNodes(prevState.nodes);\n setEdges(prevState.edges);\n setUndoStack(undoStack.slice(0, -1));\n }\n break;\n case 'redo':\n if (redoStack.length > 0) {\n const nextState = redoStack[redoStack.length - 1];\n setUndoStack([...undoStack, { nodes, edges }]);\n setNodes(nextState.nodes);\n setEdges(nextState.edges);\n setRedoStack(redoStack.slice(0, -1));\n }\n break;\n case 'delete':\n deleteSelectedNodes();\n break;\n case 'zoomIn':\n camera.zoom(1.1, canvasSize.width / 2, canvasSize.height / 2);\n setNeedsRedraw(true);\n break;\n case 'zoomOut':\n camera.zoom(0.9, canvasSize.width / 2, canvasSize.height / 2);\n setNeedsRedraw(true);\n break;\n case 'resetView':\n camera.reset();\n setNeedsRedraw(true);\n break;\n case 'runWithoutDebugging':\n runScript(false);\n break;\n case 'runWithDebugging':\n runScript(true);\n break;\n case 'generateCode':\n generateCode();\n break;\n case 'toggleGrid':\n updateConfig('isGridVisible', !config.isGridVisible);\n break;\n case 'toggleMinimap':\n updateConfig('isMinimapVisible', !config.isMinimapVisible);\n break;\n case 'copy':\n setCopiedNodes([...selectedNodes]);\n break;\n case 'paste':\n if (copiedNodes.length > 0) {\n const newNodes = copiedNodes.map(node => ({\n ...node,\n id: Date.now() + Math.random(),\n x: node.x + 20,\n y: node.y + 20,\n }));\n setNodes([...nodes, ...newNodes]);\n setUndoStack([...undoStack, { nodes, edges }]);\n setRedoStack([]);\n }\n break;\n case 'cut':\n setCopiedNodes([...selectedNodes]);\n deleteSelectedNodes();\n break;\n case 'selectAll':\n setSelectedNodes([...nodes]);\n break;\n case 'projectSettings':\n openSettings();\n break;\n case 'exportImage':\n exportAsImage();\n break;\n case 'exportSVG':\n exportAsSVG();\n break;\n case 'exportJSON':\n exportAsJSON();\n break;\n case 'exportJavaScript':\n exportAsJavaScript();\n break;\n case 'toggleNodeRounding':\n toggleNodeRounding();\n break;\n case 'toggleGraphInspector':\n updateConfig('isGraphInspectorVisible', !config.isGraphInspectorVisible);\n break;\n default:\n console.log(`Unhandled menu action: ${action}`);\n }\n setMenuOpen(null);\n };\n\n const handleTabClick = (tabId) => {\n setActiveTab(tabId);\n };\n\n const handleTabClose = (tabId) => {\n if (tabId === 'untitled-1') return; // Don't close the default tab\n const newTabs = tabs.filter(tab => tab.id !== tabId);\n setTabs(newTabs);\n if (activeTab === tabId) {\n setActiveTab(newTabs[newTabs.length - 1].id);\n }\n };\n\n const openSettings = () => {\n const settingsTabId = 'settings';\n if (!tabs.some(tab => tab.id === settingsTabId)) {\n setTabs([...tabs, { id: settingsTabId, title: 'Settings', type: 'settings' }]);\n }\n setActiveTab(settingsTabId);\n };\n // #endregion\n\n // #region Code Generation\n const runScript = (debug = false) => {\n const codeGenerator = new CodeGenerator(nodes, edges);\n codeGenerator.runScript(debug);\n };\n\n const generateCode = () => {\n const codeGenerator = new CodeGenerator(nodes, edges, config.codeGenerator);\n const generatedCode = codeGenerator.generate();\n console.log('Generated Code:');\n console.log(generatedCode);\n };\n // #endregion\n\n // #region Effects\n useEffect(() => {\n if (activeTab === 'untitled-1') {\n drawCanvas();\n }\n }, [drawCanvas, activeTab, needsRedraw]);\n\n useEffect(() => {\n if (activeTab === 'untitled-1') {\n const canvas = canvasRef.current;\n if (canvas) {\n canvas.addEventListener('wheel', handleWheel, { passive: false });\n return () => {\n canvas.removeEventListener('wheel', handleWheel);\n };\n }\n }\n }, [handleWheel, activeTab]);\n\n useEffect(() => {\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n };\n }, [selectedNodes]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n const handleResize = () => {\n setCanvasSize({ width: window.innerWidth, height: window.innerHeight });\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, []);\n\n useEffect(() => {\n renderer.setDarkTheme(config.isDarkTheme);\n renderer.setGridVisible(config.isGridVisible);\n renderer.setNodeRoundingEnabled(config.isNodeRoundingEnabled);\n setNeedsRedraw(true);\n }, [config.isDarkTheme, config.isGridVisible, config.isNodeRoundingEnabled, renderer]);\n // #endregion\n\n const toggleTheme = () => updateConfig('isDarkTheme', !config.isDarkTheme);\n const toggleGrid = () => updateConfig('isGridVisible', !config.isGridVisible);\n const toggleMinimap = () => updateConfig('isMinimapVisible', !config.isMinimapVisible);\n const toggleNodeRounding = () => updateConfig('isNodeRoundingEnabled', !config.isNodeRoundingEnabled);\n\n const updateCodeGeneratorSettings = (setting, value) => {\n updateConfig(`codeGenerator.${setting}`, value);\n };\n\n const updateConfig = (path, value) => {\n setConfig(prevConfig => {\n const newConfig = { ...prevConfig };\n const parts = path.split('.');\n let current = newConfig;\n\n for (let i = 0; i < parts.length - 1; i++) {\n current[parts[i]] = { ...current[parts[i]] };\n current = current[parts[i]];\n }\n\n current[parts[parts.length - 1]] = value;\n return newConfig;\n });\n };\n\n // #region Export Functions\n const exportAsImage = () => {\n const canvas = canvasRef.current;\n canvas.toBlob((blob) => {\n saveAs(blob, 'visual_script.png');\n });\n };\n\n const exportAsSVG = () => {\n const svgNS = \"http://www.w3.org/2000/svg\";\n const svg = document.createElementNS(svgNS, \"svg\");\n const canvasRect = canvasRef.current.getBoundingClientRect();\n svg.setAttribute(\"width\", canvasRect.width);\n svg.setAttribute(\"height\", canvasRect.height);\n svg.setAttribute(\"viewBox\", `0 0 ${canvasRect.width} ${canvasRect.height}`);\n\n // Create a background rectangle\n const background = document.createElementNS(svgNS, \"rect\");\n background.setAttribute(\"width\", \"100%\");\n background.setAttribute(\"height\", \"100%\");\n background.setAttribute(\"fill\", config.isDarkTheme ? \"#2d2d2d\" : \"#e0e0e0\");\n svg.appendChild(background);\n\n // Draw grid if visible\n if (config.isGridVisible) {\n const gridGroup = document.createElementNS(svgNS, \"g\");\n gridGroup.setAttribute(\"stroke\", config.isDarkTheme ? \"#3a3a3a\" : \"#d0d0d0\");\n gridGroup.setAttribute(\"stroke-width\", \"1\");\n\n for (let x = 0; x <= canvasRect.width; x += renderer.GRID_SIZE) {\n const line = document.createElementNS(svgNS, \"line\");\n line.setAttribute(\"x1\", x);\n line.setAttribute(\"y1\", 0);\n line.setAttribute(\"x2\", x);\n line.setAttribute(\"y2\", canvasRect.height);\n gridGroup.appendChild(line);\n }\n\n for (let y = 0; y <= canvasRect.height; y += renderer.GRID_SIZE) {\n const line = document.createElementNS(svgNS, \"line\");\n line.setAttribute(\"x1\", 0);\n line.setAttribute(\"y1\", y);\n line.setAttribute(\"x2\", canvasRect.width);\n line.setAttribute(\"y2\", y);\n gridGroup.appendChild(line);\n }\n\n svg.appendChild(gridGroup);\n }\n\n // Draw edges\n const edgeGroup = document.createElementNS(svgNS, \"g\");\n edges.forEach(edge => {\n const startNode = nodes.find(n => n.id === edge.start.nodeId);\n const endNode = nodes.find(n => n.id === edge.end.nodeId);\n if (startNode && endNode) {\n const startDimensions = renderer.getNodeDimensions(startNode, canvasRef.current.getContext('2d'));\n const endDimensions = renderer.getNodeDimensions(endNode, canvasRef.current.getContext('2d'));\n\n const startPort = edge.start.isInput\n ? { x: startNode.x, y: startNode.y + startDimensions.portStartY + edge.start.index * 20 }\n : { x: startNode.x + startDimensions.width, y: startNode.y + startDimensions.portStartY + edge.start.index * 20 };\n const endPort = edge.end.isInput\n ? { x: endNode.x, y: endNode.y + endDimensions.portStartY + edge.end.index * 20 }\n : { x: endNode.x + endDimensions.width, y: endNode.y + endDimensions.portStartY + edge.end.index * 20 };\n\n const dx = endPort.x - startPort.x;\n const controlPoint1 = { x: startPort.x + dx * 0.5, y: startPort.y };\n const controlPoint2 = { x: endPort.x - dx * 0.5, y: endPort.y };\n\n const path = document.createElementNS(svgNS, \"path\");\n path.setAttribute(\"d\", `M ${startPort.x} ${startPort.y} C ${controlPoint1.x} ${controlPoint1.y}, ${controlPoint2.x} ${controlPoint2.y}, ${endPort.x} ${endPort.y}`);\n path.setAttribute(\"fill\", \"none\");\n path.setAttribute(\"stroke\", \"#666\");\n path.setAttribute(\"stroke-width\", \"2\");\n edgeGroup.appendChild(path);\n }\n });\n svg.appendChild(edgeGroup);\n\n // Draw nodes\n const nodeGroup = document.createElementNS(svgNS, \"g\");\n nodes.forEach(node => {\n const nodeType = nodeTypes[node.type];\n const { width, height, portStartY } = renderer.getNodeDimensions(node, canvasRef.current.getContext('2d'));\n\n const nodeRect = document.createElementNS(svgNS, \"rect\");\n nodeRect.setAttribute(\"x\", node.x);\n nodeRect.setAttribute(\"y\", node.y);\n nodeRect.setAttribute(\"width\", width);\n nodeRect.setAttribute(\"height\", height);\n nodeRect.setAttribute(\"fill\", nodeType.color);\n nodeRect.setAttribute(\"stroke\", selectedNodes.includes(node) ? \"#FFFF00\" : \"#000000\");\n nodeRect.setAttribute(\"stroke-width\", \"2\");\n nodeGroup.appendChild(nodeRect);\n\n // Node title\n const title = document.createElementNS(svgNS, \"text\");\n title.setAttribute(\"x\", node.x + 10);\n title.setAttribute(\"y\", node.y + 20);\n title.setAttribute(\"fill\", \"white\");\n title.setAttribute(\"font-family\", \"Arial\");\n title.setAttribute(\"font-size\", \"14px\");\n title.setAttribute(\"font-weight\", \"bold\");\n title.textContent = node.type;\n nodeGroup.appendChild(title);\n\n // Node description (simplified, not wrapping text)\n const description = document.createElementNS(svgNS, \"text\");\n description.setAttribute(\"x\", node.x + 10);\n description.setAttribute(\"y\", node.y + 40);\n description.setAttribute(\"fill\", \"white\");\n description.setAttribute(\"font-family\", \"Arial\");\n description.setAttribute(\"font-size\", \"10px\");\n description.textContent = nodeType.description;\n nodeGroup.appendChild(description);\n\n // Input and output ports\n nodeType.inputs.forEach((input, i) => {\n const circle = document.createElementNS(svgNS, \"circle\");\n circle.setAttribute(\"cx\", node.x);\n circle.setAttribute(\"cy\", node.y + portStartY + i * 20);\n circle.setAttribute(\"r\", \"5\");\n circle.setAttribute(\"fill\", \"#FFA500\");\n nodeGroup.appendChild(circle);\n\n const text = document.createElementNS(svgNS, \"text\");\n text.setAttribute(\"x\", node.x + 10);\n text.setAttribute(\"y\", node.y + portStartY + 5 + i * 20);\n text.setAttribute(\"fill\", \"white\");\n text.setAttribute(\"font-family\", \"Arial\");\n text.setAttribute(\"font-size\", \"10px\");\n text.textContent = `${input.type === 'control' ? '▶' : '●'} ${input.name}`;\n nodeGroup.appendChild(text);\n });\n\n nodeType.outputs.forEach((output, i) => {\n const circle = document.createElementNS(svgNS, \"circle\");\n circle.setAttribute(\"cx\", node.x + width);\n circle.setAttribute(\"cy\", node.y + portStartY + i * 20);\n circle.setAttribute(\"r\", \"5\");\n circle.setAttribute(\"fill\", \"#FFA500\");\n nodeGroup.appendChild(circle);\n\n const text = document.createElementNS(svgNS, \"text\");\n text.setAttribute(\"x\", node.x + width - 70);\n text.setAttribute(\"y\", node.y + portStartY + 5 + i * 20);\n text.setAttribute(\"fill\", \"white\");\n text.setAttribute(\"font-family\", \"Arial\");\n text.setAttribute(\"font-size\", \"10px\");\n text.textContent = `${output.name} ${output.type === 'control' ? '▶' : '●'}`;\n nodeGroup.appendChild(text);\n });\n });\n svg.appendChild(nodeGroup);\n\n // Convert SVG to string and save\n const serializer = new XMLSerializer();\n const svgString = serializer.serializeToString(svg);\n const blob = new Blob([svgString], { type: \"image/svg+xml;charset=utf-8\" });\n saveAs(blob, \"visual_script.svg\");\n };\n\n const exportAsJSON = () => {\n const projectData = JSON.stringify({ nodes, edges });\n const blob = new Blob([projectData], { type: 'application/json' });\n saveAs(blob, 'visual_script.json');\n };\n\n const exportAsJavaScript = () => {\n const codeGenerator = new CodeGenerator(nodes, edges, config.codeGenerator);\n const generatedCode = codeGenerator.generate();\n const blob = new Blob([generatedCode], { type: 'text/javascript' });\n saveAs(blob, 'generated_script.js');\n };\n // #endregion\n\n // #region Handle Node Context Menu Actions\n const handleNodeContextMenuAction = (action) => {\n switch (action) {\n case 'copy':\n setCopiedNodes(selectedNodes.map(node => Node.createInstance(node, nodeTypes)));\n break;\n case 'delete':\n deleteSelectedNodes();\n break;\n case 'cut':\n setCopiedNodes(selectedNodes.map(node => Node.createInstance(node, nodeTypes)));\n deleteSelectedNodes();\n break;\n case 'duplicate':\n const newNodes = selectedNodes.map(node => {\n const duplicatedNode = Node.create(node.type, node.x + 20, node.y + 20, nodeTypes);\n duplicatedNode.properties = { ...node.properties };\n duplicatedNode.label = node.label;\n return duplicatedNode;\n });\n setNodes([...nodes, ...newNodes]);\n setSelectedNodes(newNodes);\n break;\n case 'setLabel':\n setNodeLabel();\n break;\n default:\n console.log(`Unhandled node context menu action: ${action}`);\n }\n setNodeContextMenu({ ...nodeContextMenu, visible: false });\n };\n // #endregion\n\n // #region Render\n return (\n \n
\n
\n
\n {activeTab === 'untitled-1' ? (\n <>\n {config.isGraphInspectorVisible && (\n
\n \n
\n )}\n {config.isMinimapVisible && (\n
\n renderer.getNodeDimensions(node, ctx)}\n nodeTypes={nodeTypes}\n config={config}\n />\n
\n )}\n
\n \n \n \n
\n >\n ) : activeTab === 'settings' ? (\n
\n ) : null}\n
\n
\n );\n // #endregion\n};\n\nexport default VisualScripting;","import React from 'react';\nimport VisualScripting from './VisualScripting';\nimport './App.css';\n\nfunction App() {\n return (\n \n \n
\n );\n}\n\nexport default App;","import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n \n \n \n);"],"names":["b","a","autoBom","console","warn","test","type","Blob","c","d","XMLHttpRequest","open","responseType","onload","g","response","onerror","error","send","status","e","dispatchEvent","MouseEvent","document","createEvent","initMouseEvent","window","f","self","global","navigator","userAgent","saveAs","HTMLAnchorElement","prototype","h","i","URL","webkitURL","j","createElement","name","download","rel","href","origin","location","target","createObjectURL","setTimeout","revokeObjectURL","msSaveOrOpenBlob","title","body","innerText","HTMLElement","safari","FileReader","k","onloadend","result","replace","readAsDataURL","l","m","module","exports","aa","require","ca","p","arguments","length","encodeURIComponent","da","Set","ea","fa","ha","add","ia","ja","Object","hasOwnProperty","ka","la","ma","v","this","acceptsBooleans","attributeName","attributeNamespace","mustUseProperty","propertyName","sanitizeURL","removeEmptyString","z","split","forEach","toLowerCase","ra","sa","toUpperCase","ta","slice","pa","isNaN","qa","call","oa","removeAttribute","setAttribute","setAttributeNS","xlinkHref","ua","__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED","va","Symbol","for","wa","ya","za","Aa","Ba","Ca","Da","Ea","Fa","Ga","Ha","Ia","Ja","iterator","Ka","La","A","assign","Ma","Error","stack","trim","match","Na","Oa","prepareStackTrace","defineProperty","set","Reflect","construct","displayName","includes","Pa","tag","render","Qa","$$typeof","_context","_payload","_init","Ra","Sa","Ta","nodeName","Va","_valueTracker","getOwnPropertyDescriptor","constructor","get","configurable","enumerable","getValue","setValue","stopTracking","Ua","Wa","checked","value","Xa","activeElement","Ya","defaultChecked","defaultValue","_wrapperState","initialChecked","Za","initialValue","controlled","ab","bb","cb","db","ownerDocument","eb","Array","isArray","fb","options","selected","defaultSelected","disabled","gb","dangerouslySetInnerHTML","children","hb","ib","jb","textContent","kb","lb","mb","nb","namespaceURI","innerHTML","valueOf","toString","firstChild","removeChild","appendChild","MSApp","execUnsafeLocalFunction","ob","lastChild","nodeType","nodeValue","pb","animationIterationCount","aspectRatio","borderImageOutset","borderImageSlice","borderImageWidth","boxFlex","boxFlexGroup","boxOrdinalGroup","columnCount","columns","flex","flexGrow","flexPositive","flexShrink","flexNegative","flexOrder","gridArea","gridRow","gridRowEnd","gridRowSpan","gridRowStart","gridColumn","gridColumnEnd","gridColumnSpan","gridColumnStart","fontWeight","lineClamp","lineHeight","opacity","order","orphans","tabSize","widows","zIndex","zoom","fillOpacity","floodOpacity","stopOpacity","strokeDasharray","strokeDashoffset","strokeMiterlimit","strokeOpacity","strokeWidth","qb","rb","sb","style","indexOf","setProperty","keys","charAt","substring","tb","menuitem","area","base","br","col","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","ub","vb","is","wb","xb","srcElement","correspondingUseElement","parentNode","yb","zb","Ab","Bb","Cb","stateNode","Db","Eb","push","Fb","Gb","Hb","Ib","Jb","Kb","Lb","Mb","addEventListener","removeEventListener","Nb","apply","onError","Ob","Pb","Qb","Rb","Sb","Tb","Vb","alternate","return","flags","Wb","memoizedState","dehydrated","Xb","Zb","child","sibling","current","Yb","$b","ac","unstable_scheduleCallback","bc","unstable_cancelCallback","cc","unstable_shouldYield","dc","unstable_requestPaint","B","unstable_now","ec","unstable_getCurrentPriorityLevel","fc","unstable_ImmediatePriority","gc","unstable_UserBlockingPriority","hc","unstable_NormalPriority","ic","unstable_LowPriority","jc","unstable_IdlePriority","kc","lc","oc","Math","clz32","pc","qc","log","LN2","rc","sc","tc","uc","pendingLanes","suspendedLanes","pingedLanes","entangledLanes","entanglements","vc","xc","yc","zc","Ac","eventTimes","Cc","C","Dc","Ec","Fc","Gc","Hc","Ic","Jc","Kc","Lc","Mc","Nc","Oc","Map","Pc","Qc","Rc","Sc","delete","pointerId","Tc","nativeEvent","blockedOn","domEventName","eventSystemFlags","targetContainers","Vc","Wc","priority","isDehydrated","containerInfo","Xc","Yc","shift","Zc","$c","ad","bd","cd","ReactCurrentBatchConfig","dd","ed","transition","fd","gd","hd","id","Uc","stopPropagation","jd","kd","ld","md","nd","od","keyCode","charCode","pd","qd","rd","_reactName","_targetInst","currentTarget","isDefaultPrevented","defaultPrevented","returnValue","isPropagationStopped","preventDefault","cancelBubble","persist","isPersistent","wd","xd","yd","sd","eventPhase","bubbles","cancelable","timeStamp","Date","now","isTrusted","td","ud","view","detail","vd","Ad","screenX","screenY","clientX","clientY","pageX","pageY","ctrlKey","shiftKey","altKey","metaKey","getModifierState","zd","button","buttons","relatedTarget","fromElement","toElement","movementX","movementY","Bd","Dd","dataTransfer","Fd","Hd","animationName","elapsedTime","pseudoElement","Id","clipboardData","Jd","Ld","data","Md","Esc","Spacebar","Left","Up","Right","Down","Del","Win","Menu","Apps","Scroll","MozPrintableKey","Nd","Od","Alt","Control","Meta","Shift","Pd","Qd","key","String","fromCharCode","code","repeat","locale","which","Rd","Td","width","height","pressure","tangentialPressure","tiltX","tiltY","twist","pointerType","isPrimary","Vd","touches","targetTouches","changedTouches","Xd","Yd","deltaX","wheelDeltaX","deltaY","wheelDeltaY","wheelDelta","deltaZ","deltaMode","Zd","$d","ae","be","documentMode","ce","de","ee","fe","ge","he","ie","le","color","date","datetime","email","month","number","password","range","search","tel","text","time","url","week","me","ne","oe","event","listeners","pe","qe","re","se","te","ue","ve","we","xe","ye","ze","oninput","Ae","detachEvent","Be","Ce","attachEvent","De","Ee","Fe","He","Ie","Je","Ke","node","offset","nextSibling","Le","contains","compareDocumentPosition","Me","HTMLIFrameElement","contentWindow","Ne","contentEditable","Oe","focusedElem","selectionRange","documentElement","start","end","selectionStart","selectionEnd","min","defaultView","getSelection","extend","rangeCount","anchorNode","anchorOffset","focusNode","focusOffset","createRange","setStart","removeAllRanges","addRange","setEnd","element","left","scrollLeft","top","scrollTop","focus","Pe","Qe","Re","Se","Te","Ue","Ve","We","animationend","animationiteration","animationstart","transitionend","Xe","Ye","Ze","animation","$e","af","bf","cf","df","ef","ff","gf","hf","lf","mf","concat","nf","Ub","instance","listener","D","of","has","pf","qf","rf","random","sf","bind","capture","passive","n","t","J","x","u","w","F","tf","uf","parentWindow","vf","wf","na","xa","$a","ba","je","char","ke","unshift","xf","yf","zf","Af","Bf","Cf","Df","Ef","__html","Ff","Gf","clearTimeout","Hf","Promise","Jf","queueMicrotask","resolve","then","catch","If","Kf","Lf","Mf","previousSibling","Nf","Of","Pf","Qf","Rf","Sf","Tf","Uf","E","G","Vf","H","Wf","Xf","Yf","contextTypes","__reactInternalMemoizedUnmaskedChildContext","__reactInternalMemoizedMaskedChildContext","Zf","childContextTypes","$f","ag","bg","getChildContext","cg","__reactInternalMemoizedMergedChildContext","dg","eg","fg","gg","hg","jg","kg","lg","mg","ng","og","pg","qg","rg","sg","tg","ug","vg","wg","xg","yg","I","zg","Ag","Bg","elementType","deletions","Cg","pendingProps","overflow","treeContext","retryLane","Dg","mode","Eg","Fg","Gg","memoizedProps","Hg","Ig","Jg","Kg","Lg","ref","_owner","_stringRef","refs","Mg","join","Ng","Og","index","Pg","Qg","props","Rg","implementation","Sg","Tg","q","r","y","next","done","Ug","Vg","Wg","Xg","Yg","Zg","$g","ah","_currentValue","bh","childLanes","ch","dependencies","firstContext","lanes","dh","eh","context","memoizedValue","fh","gh","hh","interleaved","ih","jh","kh","updateQueue","baseState","firstBaseUpdate","lastBaseUpdate","shared","pending","effects","lh","mh","eventTime","lane","payload","callback","nh","K","oh","ph","qh","rh","sh","th","uh","vh","wh","xh","yh","tagName","zh","Ah","Bh","L","Ch","revealOrder","Dh","Eh","_workInProgressVersionPrimary","Fh","ReactCurrentDispatcher","Gh","Hh","M","N","O","Ih","Jh","Kh","Lh","P","Mh","Nh","Oh","Ph","Qh","Rh","Sh","Th","baseQueue","queue","Uh","Vh","Wh","lastRenderedReducer","action","hasEagerState","eagerState","lastRenderedState","dispatch","Xh","Yh","Zh","$h","ai","getSnapshot","bi","ci","Q","di","lastEffect","stores","ei","fi","gi","hi","ii","create","destroy","deps","ji","ki","li","mi","ni","oi","pi","qi","ri","si","ti","ui","vi","wi","xi","yi","zi","Ai","R","Bi","readContext","useCallback","useContext","useEffect","useImperativeHandle","useInsertionEffect","useLayoutEffect","useMemo","useReducer","useRef","useState","useDebugValue","useDeferredValue","useTransition","useMutableSource","useSyncExternalStore","useId","unstable_isNewReconciler","identifierPrefix","Ci","defaultProps","Di","Ei","isMounted","_reactInternals","enqueueSetState","enqueueReplaceState","enqueueForceUpdate","Fi","shouldComponentUpdate","isPureReactComponent","Gi","contextType","state","updater","Hi","componentWillReceiveProps","UNSAFE_componentWillReceiveProps","Ii","getDerivedStateFromProps","getSnapshotBeforeUpdate","UNSAFE_componentWillMount","componentWillMount","componentDidMount","Ji","message","digest","Ki","Li","Mi","WeakMap","Ni","Oi","Pi","Qi","getDerivedStateFromError","componentDidCatch","Ri","componentStack","Si","pingCache","Ti","Ui","Vi","Wi","ReactCurrentOwner","Xi","Yi","Zi","$i","aj","compare","bj","cj","dj","baseLanes","cachePool","transitions","ej","fj","gj","hj","ij","UNSAFE_componentWillUpdate","componentWillUpdate","componentDidUpdate","jj","kj","pendingContext","lj","zj","Aj","Bj","Cj","mj","nj","oj","fallback","pj","qj","sj","dataset","dgst","tj","uj","_reactRetry","rj","subtreeFlags","vj","wj","isBackwards","rendering","renderingStartTime","last","tail","tailMode","xj","Dj","S","Ej","Fj","wasMultiple","multiple","suppressHydrationWarning","onClick","onclick","size","createElementNS","autoFocus","createTextNode","T","Gj","Hj","Ij","Jj","U","Kj","WeakSet","V","Lj","W","Mj","Nj","Pj","Qj","Rj","Sj","Tj","Uj","Vj","insertBefore","_reactRootContainer","Wj","X","Xj","Yj","Zj","onCommitFiberUnmount","componentWillUnmount","ak","bk","ck","dk","ek","isHidden","fk","gk","display","hk","ik","jk","kk","__reactInternalSnapshotBeforeUpdate","src","Vk","lk","ceil","mk","nk","ok","Y","Z","pk","qk","rk","sk","tk","Infinity","uk","vk","wk","xk","yk","zk","Ak","Bk","Ck","Dk","callbackNode","expirationTimes","expiredLanes","wc","callbackPriority","ig","Ek","Fk","Gk","Hk","Ik","Jk","Kk","Lk","Mk","Nk","Ok","finishedWork","finishedLanes","Pk","timeoutHandle","Qk","Rk","Sk","Tk","Uk","mutableReadLanes","Bc","Oj","onCommitFiberRoot","mc","onRecoverableError","Wk","onPostCommitFiberRoot","Xk","Yk","$k","isReactComponent","pendingChildren","al","mutableSourceEagerHydrationData","bl","cache","pendingSuspenseBoundaries","dl","el","fl","gl","hl","il","yj","Zk","kl","reportError","ll","_internalRoot","ml","nl","ol","pl","rl","ql","unmount","unstable_scheduleHydration","splice","querySelectorAll","JSON","stringify","form","sl","usingClientEntryPoint","Events","tl","findFiberByHostInstance","bundleType","version","rendererPackageName","ul","rendererConfig","overrideHookState","overrideHookStateDeletePath","overrideHookStateRenamePath","overrideProps","overridePropsDeletePath","overridePropsRenamePath","setErrorHandler","setSuspenseHandler","scheduleUpdate","currentDispatcherRef","findHostInstanceByFiber","findHostInstancesForRefresh","scheduleRefresh","scheduleRoot","setRefreshHandler","getCurrentFiber","reconcilerVersion","__REACT_DEVTOOLS_GLOBAL_HOOK__","vl","isDisabled","supportsFiber","inject","createPortal","cl","createRoot","unstable_strictMode","findDOMNode","flushSync","hydrate","hydrateRoot","hydratedSources","_getVersion","_source","unmountComponentAtNode","unstable_batchedUpdates","unstable_renderSubtreeIntoContainer","checkDCE","err","__self","__source","Fragment","jsx","jsxs","setState","forceUpdate","escape","_status","_result","default","Children","map","count","toArray","only","Component","Profiler","PureComponent","StrictMode","Suspense","act","cloneElement","createContext","_currentValue2","_threadCount","Provider","Consumer","_defaultValue","_globalName","createFactory","createRef","forwardRef","isValidElement","lazy","memo","startTransition","unstable_act","pop","sortIndex","performance","setImmediate","startTime","expirationTime","priorityLevel","scheduling","isInputPending","MessageChannel","port2","port1","onmessage","postMessage","unstable_Profiling","unstable_continueExecution","unstable_forceFrameRate","floor","unstable_getFirstCallbackNode","unstable_next","unstable_pauseExecution","unstable_runWithPriority","delay","unstable_wrapCallback","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","globalThis","Function","_ref","menuOpen","handleMenuClick","handleMenuItemClick","config","toggleTheme","toggleNodeRounding","theme","isDarkTheme","_jsx","className","styles","menuBar","menu","_jsxs","menuItem","menuButton","active","dropdownMenu","menuItemButton","isGridVisible","isMinimapVisible","isNodeRoundingEnabled","isGraphInspectorVisible","nodeTypes","OnStart","inputs","outputs","description","Log","properties","Variable","visible","MathOperation","Condition","WhileLoop","ForLoop","ArrayOperation","ObjectOperation","HttpRequest","JSONParse","JSONStringify","Base64Encode","Base64Decode","Random","Switch","output","nodeGroups","getIconForNodeType","addNode","camera","openGroup","setOpenGroup","menuRef","getIconForGroup","group","scale","entries","_ref2","types","handleGroupClick","renderNodeButton","nodes","edges","canvasSize","getNodeDimensions","minimapRef","calculateBounds","ctx","minX","minY","maxX","maxY","max","drawEdge","edge","bounds","startNode","find","nodeId","endNode","startDimensions","endDimensions","startPort","getPortPosition","isInput","endPort","beginPath","moveTo","lineTo","strokeStyle","lineWidth","stroke","drawNode","fillStyle","fillRect","strokeRect","font","fillText","drawMinimap","minimap","minimapCtx","getContext","scaleX","scaleY","drawViewport","viewportWidth","viewportHeight","canvas","resizeObserver","ResizeObserver","observe","disconnect","backgroundColor","border","tabs","activeTab","onTabClick","onTabClose","tabContainer","tab","isActive","tabTitle","closeButton","toggleGrid","toggleMinimap","updateCodeGeneratorSettings","sectionStyle","padding","borderRadius","marginBottom","headingStyle","paddingBottom","CustomCheckbox","onChange","label","fontSize","codeGenerator","useStrict","useSemicolons","useConst","generateComments","minZoom","maxZoom","move","dx","dy","zoomFactor","adjustedDx","adjustedDy","factor","centerX","centerY","oldScale","applyToContext","setTransform","screenToWorld","worldToScreen","worldX","worldY","reset","FONT_FAMILY","renderDescription","GRID_SIZE","drawGrid","canvasWidth","canvasHeight","offsetX","offsetY","gridSize","visibleLeft","visibleTop","visibleRight","visibleBottom","startX","startY","endX","endY","maxPorts","portsHeight","propertiesHeight","filter","prop","maxInputWidth","reduce","measureText","maxOutputWidth","inputSection","outputSection","portStartY","wrapText","maxWidth","words","lines","currentLine","word","drawCanvas","connecting","mousePosition","selectedNodes","clearRect","save","drawEdges","drawNodes","drawConnectionLine","restore","startDims","endDims","isRectInView","getPortY","dims","controlPoint1","controlPoint2","bezierCurveTo","startPortType","endPortType","drawArrow","atan2","angle","translate","rotate","closePath","fill","drawPortIcon","arrowX","portY","drawLabelArrow","isControl","lineLength","arc","PI","dimensions","drawNodeBody","drawNodeContent","drawNodeLabel","isSelected","drawRoundedRect","radius","quadraticCurveTo","currentHeight","drawNodeTitle","drawNodeDescription","drawNodePorts","drawNodeProperties","yPosition","getIconUnicode","startHeight","line","some","textWidth","displayValue","labelWidth","portX","triangleWidth","setDarkTheme","setGridVisible","setNodeRoundingEnabled","viewBounds","setRenderDescription","iconClass","iconMap","settings","indentLevel","hasAsyncOperations","variables","nodeOutputs","processedNodes","generate","addLine","generateImports","generateMainFunction","asyncKeyword","clear","generateNodeCodeSequence","generateNodeCode","nextNode","handleLogNode","handleVariableNode","handleMathOperationNode","condition","iterations","generateHttpRequestCode","handleJSONParseNode","handleJSONStringifyNode","handleBase64EncodeNode","handleBase64DecodeNode","handleIfNode","randomType","resultVar","getUniqueVariableName","parseFloat","parseInt","charset","probability","switchValue","getNodeInputValue","ignoreCase","cases","caseObj","caseValue","Number","caseEdge","defaultEdge","defaultNode","findNextControlNode","controlEdge","logType","inputValue","declaration","getTypedValue","operation","inputA","inputB","outputEdge","targetNode","inputIndex","inputEdge","sourceNode","method","headers","headersObj","parse","executeHttpRequest","debug","fetch","responseBody","statusCode","responseHeaders","fromEntries","getVariableOrLiteral","indentation","semicolon","shouldAddSemicolon","cleanLine","executeNode","executeJSONParse","executeJSONStringify","executeBase64Encode","executeBase64Decode","runScript","outputVar","space","baseName","counter","btoa","atob","pattern","operator","trueEdge","trueNode","falseEdge","falseNode","example1","example2","example3","example4","example5","PortItem","port","portContainer","portContainerDark","portContainerLight","portIconControl","portIconData","portInfo","portNameRow","portType","portDescription","portDescriptionDark","portDescriptionLight","PropertyInput","property","updateNodeProperty","renderParametersInput","parametersContainer","parameters","parameterRow","updateParameter","placeholder","inputDark","inputLight","typeSelect","removeParameter","removeButton","removeButtonDark","removeButtonLight","addParameter","addButton","addButtonDark","addButtonLight","renderCasesInput","casesContainer","caseRow","updateCase","removeCase","addCase","field","newParams","_","newCases","option","checkbox","checkboxDark","checkboxLight","_ref3","emptyMessage","emptyMessageDark","emptyMessageLight","container","containerDark","containerLight","header","headerDark","headerLight","headerContent","nodeIcon","nodeTitle","divider","section","sectionTitle","_Fragment","propertyContainer","propertyLabel","PORT_CONSTANTS","Node","fromJSON","nodeData","createFromExample","exampleNode","updateSwitchNodeType","switchNode","baseOutputs","caseItem","updateProperty","clone","clonedNode","isPointInside","portIndex","portYMiddle","isPortClicked","findClickedPort","toJSON","createInstance","onAction","icon","VisualScripting","canvasRef","contextMenu","setContextMenu","setNodes","setEdges","draggingNode","setDraggingNode","setConnecting","selectedNode","setSelectedNode","setMousePosition","setCanvasSize","innerWidth","innerHeight","setMenuOpen","Camera","setConfig","needsRedraw","setNeedsRedraw","renderer","Renderer","isDraggingCanvas","setIsDraggingCanvas","lastMousePosition","setLastMousePosition","undoStack","setUndoStack","redoStack","setRedoStack","copiedNodes","setCopiedNodes","setSelectedNodes","isMultiSelectMode","setIsMultiSelectMode","setTabs","setActiveTab","nodeContextMenu","setNodeContextMenu","handleMouseUp","rect","getBoundingClientRect","clickedPort","newEdge","draggedNode","clickedNode","findClickedNode","handleWheel","handleKeyDown","deleteSelectedNodes","handleKeyUp","portIconWidth","portIconHeight","portOffset","selectedNodeIds","openSettings","settingsTabId","CodeGenerator","generateCode","generatedCode","handleResize","updateConfig","path","prevConfig","newConfig","parts","exportAsImage","toBlob","blob","exportAsSVG","svgNS","svg","canvasRect","background","gridGroup","edgeGroup","nodeGroup","nodeRect","circle","svgString","XMLSerializer","serializeToString","exportAsJSON","projectData","exportAsJavaScript","position","flexDirection","tabIndex","onKeyDown","onKeyUp","MenuBar","confirm","accept","onchange","file","files","reader","content","readAsText","click","examples","exampleNodes","reconstructedEdges","prevState","nextState","newNodes","Tabs","tabId","newTabs","borderRight","GraphInspector","updatedNodes","prevSelected","right","margin","Minimap","onContextMenu","onMouseDown","portPosition","onMouseMove","onMouseUp","onMouseLeave","ContextMenu","newNode","NodeContextMenu","duplicatedNode","setNodeLabel","currentNode","newLabel","prompt","updatedNode","SettingsTab","setting","ReactDOM","getElementById","React","App"],"sourceRoot":""}
\ No newline at end of file
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy