diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c8afdf7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +.git +.gitignore +Dockerfile +README.md diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..64c2bca --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,38 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [main, master] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + environment: github-pages + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - run: npm ci + + - run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: dist + + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..4582139 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["feature/functions"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: 'src' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 68c0483..e9bb4cc 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,4 @@ fabric.properties # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij .idea/**/azureSettings.xml /node_modules/ +/dist/ diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 0000000..ad11df1 --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,54 @@ +# CodeWire – Branch & Commit Conventions + +## Branch names + +Use lowercase with a type prefix and short description: + +- **Format:** `type/short-description` (e.g. `feature/node-registry`, `fix/context-menu`) +- **Allowed types:** `feature`, `fix`, `chore`, `docs`, `refactor`, `test` +- **Protected:** `master` and `main` are allowed as-is (no validation when pushing them) + +**Examples:** `feature/visual-script-export`, `fix/compiler-edge-case`, `chore/deps` + +--- + +## Commit messages + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +- **Format:** `type(scope): short description` or `type: short description` +- **Types:** `feat`, `fix`, `chore`, `docs`, `refactor`, `style`, `test`, `perf` +- **Scope (optional):** e.g. `feat(nodes): add custom node type` +- **Length:** First line should be under 72 characters. + +**Examples:** +- `feat: add node registry and definitions` +- `fix(compiler): handle empty script output` +- `chore: update dependencies` + +--- + +## File and folder naming + +- **Domain folders:** Use lowercase for domain group folders under `src/js/` (e.g. `core`, `nodes`, `registry`, `compiler`, `editor`, `ui`, `persistence`). +- **JavaScript files:** Use camelCase (e.g. `nodeFactory.js`, `contextMenu.js`, `stage.js`, `saveAndLoad.js`). Match the main export or feature name. Use `index.js` for module entry points. +- **App layout:** App source lives under `src/`: entry point `src/index.html`, styles and images in `src/`, app JavaScript in `src/js/` (domain folders as above), app entry point at `src/js/app.js`. Third-party libraries live in `src/vendor/` (e.g. `codemirror`, `jquery`, `konva`). Use lowercase for vendor subfolders to avoid case-sensitivity issues. +- **Avoid:** Typos in filenames; mixing casing for the same concept (e.g. `Dependencies` vs `dependencies`). + +--- + +## Enforcing conventions + +After cloning the repo, install the Git hooks once: + +```bash +npm run setup:hooks +``` + +This installs: + +- **commit-msg** – Rejects commits that don’t match the commit message format. +- **pre-push** – Rejects pushes if the current branch name doesn’t match the branch format (except when pushing `master` or `main`). + +To skip hook checks in rare cases (e.g. emergency fix): +`git commit --no-verify` or `git push --no-verify` (use sparingly). diff --git a/README.md b/README.md index 4afaf6b..aa26c2a 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,43 @@ ![Forks](https://img.shields.io/github/forks/ayushk7/CodeWire?style=social)

- CodeWire + CodeWire

Try at https://ayushk7.github.io/CodeWire/ + +## Getting Started + +```bash +npm install +``` + +### Local Development + +```bash +npm run dev +``` + +Starts the Vite dev server at http://localhost:5173 with hot module replacement. + +### Production Build + +```bash +npm run build +``` + +Bundles and minifies everything into the `dist/` folder. To preview the production build locally: + +```bash +npm run preview +``` + +### Deployment + +The `dist/` folder is the deployable output — upload it to any static host (Netlify, Vercel, S3, etc.). + +For GitHub Pages, the included workflow (`.github/workflows/deploy-pages.yml`) automatically builds and deploys on push to `main`/`master`. + CodeWire is a node based editor inspired by UE4 Blueprints which helps in better visualization of the code, and faster scripting of complex and repetitive tasks. It doesn't bind to any particular language. @@ -28,130 +61,34 @@ Tutorial: ## Fibonacci Series -![Fibonacci Series](images/fib.png) +![Fibonacci Series](src/images/fib.png) ## HTTP REQUEST/Compiled Code -![HTTP REQUEST/Compiled Code](images/httpreq.png) +![HTTP REQUEST/Compiled Code](src/images/httpreq.png) ## Documentation ### Node Anatomy -![](images/Untitled%20Diagram.drawio.png) +![](src/images/Untitled%20Diagram.drawio.png) ### Adding New Nodes -1. Add the description of node in the [javascript/Nodes/nodes.js](javascript/Nodes/nodes.js) -```js - //description of the Print node - if (type == 'Print') { - nodeDescription.nodeTitle = 'Print'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Data', - defValue: "'hello'", - pinInId: null, - } - } - // styling for the node - nodeDescription.color = 'Print'; - nodeDescription.rows = 3; - nodeDescription.colums = 12; - } - - //NOTE: this same object is furthur used for serialization and deserialization of the graph, so we have some meta info like pinIds -``` -But still the node is not available in the context menu -![](images/print_ctx_menu.JPG) - - -2. To make this node available in the context menu you need to add it to the markup in [index.html](index.html) -```html -
Begin
-
Print
-
Alert
-
Confirm
-``` -Result -![](images/print_node.JPG) -3. Now only thing left is to add the logic for the node, that is what code it should generate on it's turn +With the node registry, adding a new node only requires editing one file: [src/js/registry/nodeDefinitions.js](src/js/registry/nodeDefinitions.js). -For this you need add the logic in [VisualScriptToJavascript.js](javascript/VisualScriptToJavascript/VisualScriptToJavascript.js) in coreAlgorithm(node) method +1. Call `registerNode({ id, schema, execCodegen?, exprCodegen? })` with the node's schema (inputs, outputs, exec pins, color, rows, columns). +2. Add the node id to the `registerMenuOrder()` array at the bottom of the file (use `null` for a separator). +3. If the node has execution flow, provide an `execCodegen(compiler, node)` function. +4. If the node produces an expression output, provide an `exprCodegen(compiler, inputNode)` function. -```js - case "Print": { - this.script += `console.log(${this.handleInp(inputPins[0])});\n - `; - this.coreAlgorithm(execOutPins[0]); // this tells algo to go to the next node which is connected at first pin(triangle shaped) - } +See existing definitions in [src/js/registry/nodeDefinitions.js](src/js/registry/nodeDefinitions.js) for examples (e.g. Print, Add, Branch). - // this will append the console.log(input) in the generated js -``` -Result -![](images/print_example.JPG) - - -#### NOTE: The above node only takes input, but if the node also do outputs, then that logic is needed to be added separately -Example: Add Node - -Description: -```js - if (type == 'Add') { // this type should match the entry in the context menu's markup in index.html - nodeDescription.nodeTitle = 'Add'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 0, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } -``` - -And In [VisualScriptToJavascript.js](javascript/VisualScriptToJavascript/VisualScriptToJavascript.js) - -in handleInputs() method - -```js -case "Add": { - expr = `(${this.handleInputs(inputPins[0])} + ${this.handleInputs(inputPins[1])})`; -} -// this will generate and return (inp1 + inp2) expression -``` -![](images/add_ex.JPG) +The node factory ([src/js/nodes/nodeFactory.js](src/js/nodes/nodeFactory.js)) and compiler ([src/js/compiler/compiler.js](src/js/compiler/compiler.js)) automatically pick up any registered node. diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..e2f3e51 --- /dev/null +++ b/dockerfile @@ -0,0 +1,26 @@ +# ---------- Build Stage ---------- +FROM node:22 AS builder + +WORKDIR /app + +RUN npm install -g pnpm + +COPY package.json pnpm-lock.yaml* ./ + +RUN pnpm install + +COPY . . + +RUN pnpm run build + + +# ---------- Production Stage ---------- +FROM nginx:alpine + +RUN rm -rf /usr/share/nginx/html/* + +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/index.html b/index.html deleted file mode 100644 index 70181bd..0000000 --- a/index.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - - - CodeWire - - - - - - - - - - -
-
- - -
-
- -
-
-
-
Add Variable
-
-
-
-
-
Type
- -
-
-
Name
- -
-
-
Value
- -
- - - - -
-
-
Variable List
-
-
-
-
    -
-
-
-
-
-
- -
-
-
-
-
-

Live Code

-
-
- -
-
- -
-
- - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/javascript/ColorMap/colorMap.js b/javascript/ColorMap/colorMap.js deleted file mode 100644 index 63a7328..0000000 --- a/javascript/ColorMap/colorMap.js +++ /dev/null @@ -1,29 +0,0 @@ -export const colorMap = { - 'Number': '#00d0fa', - 'String': '#ed1a95', - 'Boolean': '#f30909', - 'Array' : '#ccff33', - 'Data': '#ff7900', - 'MainLabel': '#ffffff', - 'MainLabelBox': '#3282b8', - // 'MainLabelBox': '#7209b7', - // 'MainBox': '#3c415c', - // 'MainBox': '#413D65', - 'MainBoxGradient': { - 'start': '#28313b', - 'end': '#485461', - }, - 'Text': '#ffffff', - 'Logic': '#7209b7', - 'Get': '#008000', - 'Math': '#ae2012', - 'Func': '#0353a4', - 'Begin': '#d8572a', - 'Print' : '#bc4749' -}; - - -//red: #ae2012 -//green: #008000 -//blue: #0353a4 -//violet: #7209b7 \ No newline at end of file diff --git a/javascript/ContextMenu/contextMenu.js b/javascript/ContextMenu/contextMenu.js deleted file mode 100644 index 34dcb36..0000000 --- a/javascript/ContextMenu/contextMenu.js +++ /dev/null @@ -1,185 +0,0 @@ -import { setLocationOfNode } from '../setLocationOfNode/setLocationOfNode.js' -import { Nodes } from '../Nodes/nodes.js' -import { variableList } from '../Variable/variable.js' -import { deleteProgramNode, deleteWire } from '../Delete/delete.js' - -export var ContextMenu = { - contextMenu: function (stage, layer) { - let contextMenu = document.getElementById("ctx-menu-container"); - let deleteCtxMenu = document.getElementById("delete-ctx-container"); - let getSetCtxMenu = document.getElementById("get-set-ctx-menu-container"); - let searchBar = document.getElementById("ctx-search-bar"); - let draggedVariableInfo = { - name: null, - dataType: null, - }; - function toggleContextMenu(location, show) { - if (show) { - contextMenu.classList.toggle("hidden", false); - contextMenu.style.left = location[0] + 'px'; - contextMenu.style.top = location[1] + 'px'; - searchBar.focus(); - } - else { - contextMenu.classList.toggle("hidden", true); - searchBar.value = ''; - for (let ctxItem of contextMenu.children[1].children) { - ctxItem.classList.toggle("hidden", false); - } - } - } - function toggleDeleteCtxMenu(location, show) { - if (show) { - deleteCtxMenu.classList.toggle("hidden", false); - deleteCtxMenu.style.left = location[0] + 'px'; - deleteCtxMenu.style.top = location[1] + 'px'; - - } - else { - deleteCtxMenu.classList.toggle("hidden", true); - } - } - function toggleGetSetCtxMenu(location, show) { - if (show) { - getSetCtxMenu.classList.toggle("hidden", false); - getSetCtxMenu.style.left = location[0] + 'px'; - getSetCtxMenu.style.top = location[1] + 'px'; - } - else { - getSetCtxMenu.classList.toggle("hidden", true); - } - } - ContextMenu.addEventToCtxMenuItems = function (e) { - e.addEventListener('click', function () { - makeNode(e, stage, layer, toggleContextMenu); - }); - } - searchBar.addEventListener("input", (e) => { - let key = e.target.value.toLowerCase(); - // /\bhe/gmi - // let patt = /\b(key)/gi; - // let patt = new RegExp(`${key}`, "gis"); - // console.log(patt); - for (let ctxItem of contextMenu.children[1].children) { - if (ctxItem.innerHTML.toString().toLowerCase().includes(key)) { - ctxItem.classList.toggle("hidden", false); - } - else { - ctxItem.classList.toggle("hidden", true); - } - } - - }); - - - - - for (let e of contextMenu.children[1].children) { - this.addEventToCtxMenuItems(e); - }; - - // let alreadyPresent = []; // to prevent adding multiple eventListeners - stage.on('contextmenu', function (e) { - e.evt.preventDefault(); - if (e.target === stage) { - let availY = stage.getContainer().getBoundingClientRect().height - e.evt.clientY; - let offY = 0, offX = 0; - if (availY <= 260) { - offY = -260; - } - let availX = stage.getContainer().getBoundingClientRect().width - e.evt.clientX; - if (availX <= 200) { - offX = -200; - } - toggleContextMenu([e.evt.clientX + offX, e.evt.clientY + offY], true); - } - else { - - toggleDeleteCtxMenu([e.evt.clientX - 130, e.evt.clientY - 35], true); - // console.log("xx"); - deleteCtxMenu.onclick = function () { - // console.log("x"); - // console.log(e); - if (e.target.getParent().name() == 'aProgramNodeGroup') { - deleteProgramNode(e, layer, stage); - stage.draw(); - } - else if (e.target.name() == "isConnection") { - deleteWire(e.target); - stage.draw(); - } - toggleDeleteCtxMenu([e.evt.clientX - 130, e.evt.clientY - 35], false); - } - }; - - }); - stage.on('click', function (e) { - toggleContextMenu([e.evt.clientX, e.evt.clientY], false); - toggleDeleteCtxMenu([], false); - toggleGetSetCtxMenu([], false); - }); - document.addEventListener("click", (e) => { - if (e.target !== stage.getContainer() && e.target !== searchBar) - toggleContextMenu([0, 0], false); - if (e.target !== stage.getContainer()) { - toggleDeleteCtxMenu([], false); - toggleGetSetCtxMenu([], false); - } - }); - - getSetCtxMenu.addEventListener("click", (e) => { - let nodeType = e.target.innerHTML + " " + draggedVariableInfo.name; - let xx = e.target.parentElement.getBoundingClientRect().x - stage.getContainer().getBoundingClientRect().x; - let yy = e.target.parentElement.getBoundingClientRect().y - stage.getContainer().getBoundingClientRect().y; - if (e.target.innerHTML == "Get") { - Nodes.CreateNode(nodeType, { x: xx, y: yy }, layer, stage, "Get", draggedVariableInfo.dataType, null); - } - else { - Nodes.CreateNode(nodeType, { x: xx, y: yy }, layer, stage, "Set", draggedVariableInfo.dataType, null); - } - }); - - - stage.getContainer().addEventListener('dragenter', (e) => { - e.preventDefault(); - e.stopPropagation(); - }); - stage.getContainer().addEventListener('dragover', (e) => { - e.preventDefault(); - e.stopPropagation(); - }); - stage.getContainer().addEventListener('drop', (e) => { - e.preventDefault(); - if (e.dataTransfer.getData("variableName")) { - toggleGetSetCtxMenu([e.clientX, e.clientY], true); - draggedVariableInfo = { - name: e.dataTransfer.getData("variableName"), - dataType: e.dataTransfer.getData("dataType"), - } - } - e.stopPropagation(); - }); - } -} - - - -function makeNode(e, stage, layer, toggleContextMenu) { - let xx = e.parentElement.getBoundingClientRect().x - stage.getContainer().getBoundingClientRect().x; - let yy = e.parentElement.getBoundingClientRect().y - stage.getContainer().getBoundingClientRect().y; - let node = undefined; - let dataType; - if (e.dataset.datatype) - dataType = e.dataset.datatype; - let tmp = e.innerHTML.split(" "); - let isGetSet = ""; - if (tmp[0] == "Get") - isGetSet = "Get"; - else if (tmp[0] == "Set") - isGetSet = "Set"; - let defValue = null; - // console.log(e.innerHTML); - Nodes.CreateNode(e.innerHTML, { x: xx, y: yy }, layer, stage, isGetSet, dataType, defValue); - layer.draw(); - toggleContextMenu([], false); -} diff --git a/javascript/Nodes/nodes.js b/javascript/Nodes/nodes.js deleted file mode 100644 index e5e4a49..0000000 --- a/javascript/Nodes/nodes.js +++ /dev/null @@ -1,2321 +0,0 @@ -import { InputBox } from '../InputBox/InputBox.js' -import { colorMap } from '../ColorMap/colorMap.js' -import { setLocationOfNode } from '../setLocationOfNode/setLocationOfNode.js'; -let placeLocation = function (location) { - //"this" is stage - return { - x: (location.x - this.x()) / this.scaleX(), - y: (location.y - this.y()) / this.scaleY() - }; -} -export var Nodes = { - countNodes: 0, - getExecPin: function (inType, helper, layer) { - // let pointsExecIn = [0, 0, -14, -7, -14, 7]; - // let pointsExecOut = [] - let pin = new Konva.Line({ - points: [0, 0, -14, -7, -14, 7], - stroke: 'white', - strokeWidth: 2, - hitStrokeWidth: 10, - closed: true, - helper: helper, - name: 'pin', - offsetX: (inType) ? -14 : 0, - pinType: (inType) ? 'exec-in' : 'exec-out', - pinDataType: null, - fill: '', - }); - pin.on("mouseenter", () => { - pin.strokeWidth(4); - layer.draw(); - }); - pin.on("mouseleave", () => { - pin.strokeWidth(2); - layer.draw(); - }); - pin.on("wireremoved", (e) => { - if (e.isPinEmpty) { - pin.fill('transparent'); - } - }); - pin.on("wireconnected", (e) => { - pin.fill("white"); - }); - pin.on("wiringstart", (e) => { - pin.fill("white"); - layer.draw(); - }); - return pin; - }, - getRectBlock: function (height, width) { - let rect = new Konva.Rect({ - height: height, - width: width, - // fill: colorMap['MainBox'], - opacity: 0.8, - cornerRadius: 5, - shadowColor: 'black', - shadowBlur: 15, - shadowOffset: { x: 15, y: 15 }, - shadowOpacity: 0.5, - fillLinearGradientStartPoint: { x: 0, y: 0 }, - fillLinearGradientEndPoint: { x: width, y: height }, - fillLinearGradientColorStops: [0, colorMap['MainBoxGradient']['start'], 1, colorMap['MainBoxGradient']['end']], - // fillLinearGradientColorStops: [0, '#12100e', 1, '#2b4162'], - - // strokeWidth: [10, 10, 110, 0], - }); - return rect; - }, - getInputPin: function (inType, helper, type, layer) { - let pin = new Konva.Circle({ - radius: 7, - stroke: colorMap[type], - strokeWidth: 2, - hitStrokeWidth: 10, - name: 'pin', - pinType: (inType) ? 'inp' : 'outp', - pinDataType: type, - offsetX: (inType) ? -7 : 7, - helper: helper, - fill: '', - }); - pin.on("mouseenter", () => { - pin.strokeWidth(4); - layer.draw(); - }); - pin.on("mouseleave", () => { - pin.strokeWidth(2); - layer.draw(); - }); - pin.on("wireremoved", (e) => { - if (e.isPinEmpty) { - pin.fill('transparent'); - } - }); - pin.on("wireconnected", (e) => { - pin.fill(`${colorMap[type]}`); - }); - pin.on("wiringstart", (e) => { - pin.fill(`${colorMap[type]}`); - layer.draw(); - }); - return pin; - }, - // getOutputPin: function(){ - // let pin = new Konva.Circle({ - // radius: 7, - // stroke: 'yellow', - // strokeWidth: '2', - // name: 'pin', - // pinType: 'outp', - // }); - // return pin; - // }, - getLabel: function (text, size, width, color) { - let rect = new Konva.Rect({ - width: width, - height: size + 3, - fill: colorMap[color], - cornerRadius: [5, 5, 0, 0], - // fillLinearGradientStartPoint: { x: 0, y: 0 }, - // fillLinearGradientEndPoint: { x: width, y: size + 3 }, - // fillLinearGradientColorStops: [0, colorMap[color], 1, 'rgba(0, 0, 0, 0)'], - // fillRadialGradientStartPoint: {x: 0, y: 0}, - // fillRadialGradientEndPoint: { x: 30, y: 0 }, - // fillRadialGradientColorStops: [0, colorMap[color], 1, '#2d3436'], - // fillRadialGradientStartRadius: size / 3, - // fillRadialGradientEndRadius: 100, - - // fillLinearGradientColorStops: [0, '#9e768f', 1, '#ff4e00'], - - // #ec9f05 #ff4e00 - }); - let label = new Konva.Text({ - text: text, - fontSize: size - 5, - fontFamily: 'Verdana', - fill: colorMap['MainLabel'], - width: width, - // height: size + 3, - y: 2, - align: 'left', - padding: 3, - // padding: 10 - }); - return { bg: rect, text: label }; - }, - getPinCounts: function (nodeDescription) { - let inputPinCounts = 0; - let outputPinCounts = 0; - if (nodeDescription.execIn) - inputPinCounts++; - if (nodeDescription.inputs) { - inputPinCounts += Object.keys(nodeDescription.inputs).length; - } - - //For outputs - if (nodeDescription.execOut) { - outputPinCounts += Object.keys(nodeDescription.execOut).length; - } - if (nodeDescription.outputs) { - outputPinCounts += Object.keys(nodeDescription.outputs).length; - - } - return Math.max(inputPinCounts, outputPinCounts); - }, - // getEditableTextBox: function (type, stage, index) { - // let rect = new Konva.Rect({ - // width: (type == 'Boolean') ? 14 : 50, - // height: 14, - // stroke: colorMap[type], - // strokeWidth: 1, - // }); - // return rect; - // }, - getInputLabel: function (labelText, isInput) { - let text = new Konva.Text({ - // width: 40, - height: 14, - text: labelText, - fontSize: 11, - fontFamily: 'Verdana', - fill: colorMap['Text'], - }); - if (isInput) - text.offsetX(0); - else - text.offsetX(text.width()); - // text.off() - return text; - }, - getExecOutTitle: function (labelText) { - let text = new Konva.Text({ - height: 14, - fontSize: 11, - text: labelText, - fontFamily: 'Verdana', - fill: "white", - }); - text.offsetX(text.width()); - return text; - }, - optimizeDrag: function (grp, stage, layer) { - let dragLayer = stage.findOne('#dragLayer'); - let wireLayer = stage.findOne('#wireLayer'); - grp.on('dragstart', () => { - grp.moveTo(dragLayer); - for (let each of grp.customClass.execInPins) { - for (let aWire of each.wire) { - aWire.moveTo(dragLayer); - } - } - for (let each of grp.customClass.execOutPins) { - if (each.wire) - each.wire.moveTo(dragLayer); - } - for (let each of grp.customClass.inputPins) { - if (each.wire) - each.wire.moveTo(dragLayer); - } - for (let each of grp.customClass.outputPins) { - for (let aWire of each.wire) { - aWire.moveTo(dragLayer); - } - } - wireLayer.draw(); - dragLayer.draw(); - layer.draw(); - // try { - // if (layer.hasChildren()) - // layer.cache(); - // if (wireLayer.hasChildren()) - // wireLayer.cache(); - // } - // catch (err) { - - // } - }) - grp.on('dragend', () => { - grp.moveTo(layer); - for (let each of grp.customClass.execInPins) { - for (let aWire of each.wire) { - aWire.moveTo(wireLayer); - } - } - for (let each of grp.customClass.execOutPins) { - if (each.wire) - each.wire.moveTo(wireLayer); - } - for (let each of grp.customClass.inputPins) { - if (each.wire) - each.wire.moveTo(wireLayer); - } - for (let each of grp.customClass.outputPins) { - for (let aWire of each.wire) { - aWire.moveTo(wireLayer); - } - } - // layer.clearCache(); - // wireLayer.clearCache(); - wireLayer.draw(); - dragLayer.draw(); - layer.draw(); - }); - }, - getBorderRect: function (height, width) { - let rect = new Konva.Rect({ - height: height, - width: width, - fill: 'transparent', - stroke: '#dbd8e3', - strokeWidth: 0, - cornerRadius: 5, - name: 'borderbox', - }); - rect.off('click mouseover mouseenter mouseleave'); - return rect; - }, - ProgramNode: class { - constructor(nodeDescription, location, layer, stage) { - - - - this.grp = new Konva.Group({ - draggable: true, - name: "aProgramNodeGroup", - }); - if (nodeDescription.nodeTitle == 'Begin') { - this.grp.id('Begin'); - } - this.grp.customClass = this; - // this.grp.on('dblclick', (e) => { - // console.table(e.currentTarget.customClass); - // }) - this.nodeDescription = nodeDescription; - let relativePosition = placeLocation.bind(stage); - let maxOfPinsOnEitherSide = Nodes.getPinCounts(nodeDescription); - let height = maxOfPinsOnEitherSide * 50 + 15; - let width = nodeDescription.colums * 15; - this.grp.position(relativePosition(location)); - let rect = Nodes.getRectBlock(height, width); - this.grp.add(rect); - let borderRect = Nodes.getBorderRect(height, width); - let titleLabel = Nodes.getLabel(nodeDescription.nodeTitle, 20, width, nodeDescription.color); - this.grp.add(titleLabel.bg); - this.grp.add(titleLabel.text); - this.grp.add(borderRect); - - this.grp.on("mouseover", (e) => { - // console.log(e); - // if(shape == this.grp) - borderRect.strokeWidth(1); - layer.draw(); - }); - this.grp.on("mouseleave", (e) => { - // rect.opacity(0.9); - // rect.shadowOffset({ x: 15, y: 15 }); - // this.grp.scale(1); - // this.grp.filters([]); - borderRect.strokeWidth(0); - layer.draw(); - }); - this.grp.on('mousedown', (e) => { - rect.shadowBlur(25); - // rect.shadowOffset({ x: 25, y: 25 }); - layer.draw(); - }) - this.grp.on('mouseup', (e) => { - rect.shadowBlur(15); - // rect.shadowOffset({ x: 15, y: 15 }); - layer.draw(); - }) - /****/ - - Nodes.optimizeDrag(this.grp, stage, layer); - - /****/ - // titleLabel.offsetX(titleLabel.width() / 2); - let inputPinsPlaced = 0, outputPinsPlaced = 0; - this.execInPins = []; - if (nodeDescription.execIn == true) { - let execInPin = Nodes.getExecPin(true, 'exec-in-0', layer); - execInPin.position({ x: 7, y: 44 }); - if (nodeDescription.pinExecInId == null) { - execInPin.id(`${execInPin._id}`); - } - else { - execInPin.id(nodeDescription.pinExecInId); - } - this.nodeDescription.pinExecInId = execInPin.id(); - this.grp.add(execInPin); - let tmp = { - thisNode: execInPin, - wire: [], - } - this.execInPins.push(tmp); - inputPinsPlaced = 1; - } - - let X = nodeDescription.nodeTitle.split(" "); - this.type = { - isGetSet: (X[0] == 'Get' || X[0] == 'Set'), - typeOfNode: nodeDescription.nodeTitle, - } - this.execOutPins = []; - if (nodeDescription.execOut) { - Object.keys(nodeDescription.execOut).forEach((value, index) => { - let execOutPin = Nodes.getExecPin(false, `exec-out-${index}`, layer); - execOutPin.position({ x: width - 7, y: 44 + nodeDescription.execOut[value].outOrder * 39 }); - if (nodeDescription.execOut[value].pinExecOutId == null) { - execOutPin.id(`${execOutPin._id}`); - } - else { - execOutPin.id(nodeDescription.execOut[value].pinExecOutId); - } - this.nodeDescription.execOut[value].pinExecOutId = execOutPin.id(); - this.grp.add(execOutPin); - if (nodeDescription.execOut[value].execOutTitle) { - let exLabel = Nodes.getExecOutTitle(nodeDescription.execOut[value].execOutTitle); - exLabel.position({ x: width - 28, y: 44 + nodeDescription.execOut[value].outOrder * 39 - 4 }); - this.grp.add(exLabel); - } - let tmp = { - thisNode: execOutPin, - wire: null, - title: value.execOutTitle, - } - this.execOutPins.push(tmp); - outputPinsPlaced++; - }); - } - this.inputPins = []; - if (nodeDescription.inputs) { - Object.keys(nodeDescription.inputs).forEach((value, index) => { - let inputPin = Nodes.getInputPin(true, `inp-${index}`, nodeDescription.inputs[value].dataType, layer); - inputPin.position({ x: 7, y: 44 + 39 * inputPinsPlaced }); - if (nodeDescription.inputs[value].pinInId == null) { - inputPin.id(`${inputPin._id}`); - } - else { - inputPin.id(nodeDescription.inputs[value].pinInId); - } - this.nodeDescription.inputs[value].pinInId = inputPin.id(); - // iprect.position({ x: 28, y: 44 + 39 * inputPinsPlaced - 2 }); - let iprect = null; - let iplabel = Nodes.getInputLabel(nodeDescription.inputs[value].inputTitle, true); - iplabel.position({ x: 28, y: 44 + 39 * inputPinsPlaced - 4 }); - if (nodeDescription.inputs[value].isInputBoxRequired !== false) { - // console.log(nodeDescription.inputs, this.nodeDescription.inputs); - iprect = new InputBox(stage, layer, nodeDescription.inputs[value].dataType, this.grp, { x: 28, y: 44 + 39 * inputPinsPlaced - 2 }, colorMap, inputPin, iplabel, inputPinsPlaced, nodeDescription.inputs[value], this.nodeDescription.inputs[value]); - iplabel.position({ x: 28, y: 44 + 39 * inputPinsPlaced - 14 }); - } - this.grp.add(iplabel); - this.grp.add(inputPin); - // this.grp.add(iprect); - let tmp = { - thisNode: inputPin, - wire: null, - textBox: iprect, - value: null, - title: value.inputTitle, - } - this.inputPins.push(tmp); - inputPinsPlaced++; - }); - } - this.outputPins = []; - if (nodeDescription.outputs) { - Object.keys(nodeDescription.outputs).forEach((value, index) => { - let outputPin = Nodes.getInputPin(false, `out-${index}`, nodeDescription.outputs[value].dataType, layer); - outputPin.position({ x: width - 7, y: 44 + 39 * nodeDescription.outputs[value].outOrder }); - if (nodeDescription.outputs[value].pinOutId == null) { - outputPin.id(`${outputPin._id}`); - } - else { - outputPin.id(nodeDescription.outputs[value].pinOutId); - } - nodeDescription.outputs[value].pinOutId = outputPin.id(); - this.grp.add(outputPin); - let outLabel = Nodes.getInputLabel(nodeDescription.outputs[value].outputTitle, false); - outLabel.position({ x: width - 28, y: 44 + 39 * nodeDescription.outputs[value].outOrder - 4 }) - this.grp.add(outLabel); - let tmp = { - wire: [], - value: null, - title: value.outputTitle, - } - this.outputPins.push(tmp); - outputPinsPlaced++; - }) - }; - // this.grp.cache(); - layer.add(this.grp); - layer.draw(); - layer.draw(); - // console.log(JSON.parse(JSON.stringify(this.grp))); - } - }, - - - - - - CreateNode: function (type, location, layer, stage, isGetSet, dataType, defValue) { - let nodeDescription = {}; - if (type == 'Begin') { - nodeDescription.nodeTitle = 'Begin'; - nodeDescription.execIn = false; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - } - }; - nodeDescription.color = 'Begin'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Print') { - nodeDescription.nodeTitle = 'Print'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Data', - defValue: "'hello'", - pinInId: null, - } - } - nodeDescription.color = 'Print'; - nodeDescription.rows = 3; - nodeDescription.colums = 12; - } - if (type == 'Alert') { - nodeDescription.nodeTitle = 'Alert'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Data', - defValue: "'hello'", - pinInId: null, - } - } - nodeDescription.color = 'Print'; - nodeDescription.rows = 3; - nodeDescription.colums = 12; - } - if (type == 'Confirm') { - nodeDescription.nodeTitle = 'Confirm'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Message', - dataType: 'String', - defValue: "'Ok'", - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Ok?', - dataType: 'Boolean', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Print'; - nodeDescription.rows = 3; - nodeDescription.colums = 12; - } - if (type == 'Prompt') { - nodeDescription.nodeTitle = 'Prompt'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Message', - dataType: 'String', - defValue: "'Ok'", - pinInId: null, - }, - input1: { - inputTitle: 'Default', - dataType: 'String', - defValue: "'Yes'", - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Ok?', - dataType: 'Boolean', - pinOutId: null, - outOrder: 1, - }, - output1: { - outputTitle: 'Value', - dataType: 'String', - pinOutId: null, - outOrder: 2, - }, - } - nodeDescription.color = 'Print'; - nodeDescription.rows = 3; - nodeDescription.colums = 12; - } - if (type == 'If/Else') { - nodeDescription.nodeTitle = 'If/Else'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: 'True', - pinExecOutId: null, - outOrder: 0, - }, - execOut1: { - execOutTitle: 'False', - pinExecOutId: null, - outOrder: 1, - }, - execOut2: { - execOutTitle: 'Done', - pinExecOutId: null, - outOrder: 2, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Bool', - dataType: 'Boolean', - defValue: true, - pinInId: null, - } - } - nodeDescription.color = 'Logic'; - nodeDescription.rows = 3; - nodeDescription.colums = 12; - } - if (type == 'Add') { - nodeDescription.nodeTitle = 'Add'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 0, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Modulo') { - nodeDescription.nodeTitle = 'Modulo'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 2, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Subtract') { - nodeDescription.nodeTitle = 'Subtract'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 0, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Multiply') { - nodeDescription.nodeTitle = 'Multiply'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Divide') { - nodeDescription.nodeTitle = 'Divide'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Power') { - nodeDescription.nodeTitle = 'Power'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 2, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 2, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Ceil') { - nodeDescription.nodeTitle = 'Ceil'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Floor') { - nodeDescription.nodeTitle = 'Floor'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - - if (type == 'WhileLoop') { - nodeDescription.nodeTitle = 'WhileLoop'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.inputs = { - input0: { - inputTitle: 'Bool', - dataType: 'Boolean', - defValue: false, - pinInId: null, - }, - } - nodeDescription.execOut = { - execOut0: { - execOutTitle: 'Loop Body', - pinExecOutId: null, - outOrder: 0, - - }, - execOut1: { - execOutTitle: 'Completed', - pinExecOutId: null, - outOrder: 1, - - } - } - nodeDescription.color = 'Logic'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - - if (type == 'OR') { - nodeDescription.nodeTitle = 'OR'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Boolean', - defValue: true, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Boolean', - defValue: true, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'AND') { - nodeDescription.nodeTitle = 'AND'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Boolean', - defValue: true, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Boolean', - defValue: true, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'XOR') { - nodeDescription.nodeTitle = 'XOR'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Boolean', - defValue: true, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Boolean', - defValue: true, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'bXOR') { - nodeDescription.nodeTitle = 'bXOR'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'bOR') { - nodeDescription.nodeTitle = 'bOR'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'bAND') { - nodeDescription.nodeTitle = 'bAND'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'bXOR') { - nodeDescription.nodeTitle = 'XOR'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'bNEG') { - nodeDescription.nodeTitle = 'bNEG'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "Swap") { - nodeDescription.nodeTitle = 'Swap'; - nodeDescription.execIn = true, - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Ref1', - dataType: 'Data', - isInputBoxRequired: false, - pinInId: null, - }, - input1: { - inputTitle: 'Ref2', - dataType: 'Data', - isInputBoxRequired: false, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Ref1', - dataType: 'Data', - pinOutId: null, - outOrder: 1, - - }, - output1: { - outputTitle: 'Ref2', - dataType: 'Data', - pinOutId: null, - outOrder: 2, - - } - } - nodeDescription.color = 'Func'; - - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == "Equals") { - nodeDescription.nodeTitle = 'Equals'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Data', - defValue: 0, - pinInId: null, - - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Data', - defValue: 0, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "Not Equals") { - nodeDescription.nodeTitle = 'Not Equals'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Data', - defValue: 0, - pinInId: null, - - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Data', - defValue: 0, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "LessEq") { - nodeDescription.nodeTitle = 'LessEq'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "Less") { - nodeDescription.nodeTitle = 'Less'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "Greater") { - nodeDescription.nodeTitle = 'Greater'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "GreaterEq") { - nodeDescription.nodeTitle = 'GreaterEq'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'NEG') { - nodeDescription.nodeTitle = 'NEG'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Boolean', - defValue: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "Max(Num)") { - nodeDescription.nodeTitle = 'Max(Num)'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 0, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Max', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "Min(Num)") { - nodeDescription.nodeTitle = 'Min(Num)'; - nodeDescription.inputs = { - input0: { - inputTitle: 'ValueA', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'ValueB', - dataType: 'Number', - defValue: 0, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Min', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (isGetSet == "Set") { - let defaultValueByType = { - "Number": 0, - "Boolean": true, - "String": "'hello'", - "Array": '[]', - } - nodeDescription.nodeTitle = type; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: dataType, - defValue: defaultValueByType[dataType], - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Value(Ref)', - dataType: dataType, - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (isGetSet == "Get") { - nodeDescription.nodeTitle = type; - nodeDescription.outputs = { - output0: { - outputTitle: 'Value(Ref)', - dataType: dataType, - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Get'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Random') { - nodeDescription.nodeTitle = 'Random'; - nodeDescription.outputs = { - output0: { - outputTitle: 'Random[0,1)', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Math'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'ForLoop') { - nodeDescription.nodeTitle = 'ForLoop'; - nodeDescription.pinExecInId = null; - nodeDescription.execIn = true; - nodeDescription.execOut = { - execOut0: { - execOutTitle: 'Loop Body', - pinExecOutId: null, - outOrder: 0, - }, - execOut1: { - execOutTitle: 'Completed', - pinExecOutId: null, - outOrder: 2, - } - } - nodeDescription.inputs = { - input0: { - inputTitle: 'From', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'To(Excl)', - dataType: 'Number', - defValue: 10, - pinInId: null, - }, - input2: { - inputTitle: 'Increment', - dataType: 'Number', - defValue: 1, - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Index', - dataType: 'Number', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Logic'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == 'ForEachLoop') { - nodeDescription.nodeTitle = 'ForEachLoop'; - nodeDescription.pinExecInId = null; - nodeDescription.execIn = true; - nodeDescription.execOut = { - execOut0: { - execOutTitle: 'Loop Body', - pinExecOutId: null, - outOrder: 0, - }, - execOut1: { - execOutTitle: 'Completed', - pinExecOutId: null, - outOrder: 4, - } - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Value', - dataType: 'Data', - pinOutId: null, - outOrder: 1, - }, - output1: { - outputTitle: 'Index', - dataType: 'Number', - pinOutId: null, - outOrder: 2, - }, - output2: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 3, - } - } - nodeDescription.color = 'Logic'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == "Break") { - nodeDescription.nodeTitle = 'Break'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.color = 'Logic'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == "Continue") { - nodeDescription.nodeTitle = 'Continue'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.color = 'Logic'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Length') { - nodeDescription.nodeTitle = 'Length'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Value', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Get'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'isEmpty') { - nodeDescription.nodeTitle = 'isEmpty'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Result', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Get'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Reverse') { - nodeDescription.nodeTitle = 'Reverse'; - nodeDescription.pinExecInId = null; - nodeDescription.execIn = true; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - } - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Front') { - nodeDescription.nodeTitle = 'Front'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Front(Ref)', - dataType: 'Data', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Get'; - nodeDescription.rows = 2; - nodeDescription.colums = 11; - } - if (type == 'Sort(Num)') { - nodeDescription.nodeTitle = 'Sort(Num)'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - input1: { - inputTitle: 'Increasing', - dataType: 'Boolean', - pinInId: null, - defValue: true, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == 'Back') { - nodeDescription.nodeTitle = 'Back'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Back(Ref)', - dataType: 'Data', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Get'; - nodeDescription.rows = 2; - nodeDescription.colums = 11; - } - if (type == 'GetByPos') { - nodeDescription.nodeTitle = 'GetByPos'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Pos', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Value(Ref)', - dataType: 'Data', - pinOutId: null, - outOrder: 0, - } - } - nodeDescription.color = 'Get'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == 'SetByPos') { - nodeDescription.nodeTitle = 'SetByPos'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Pos', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'Value', - dataType: 'Data', - defValue: 1, - pinInId: null, - - }, - input2: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Value(Ref)', - dataType: 'Data', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 4; - nodeDescription.colums = 12; - } - if (type == 'Insert') { - nodeDescription.nodeTitle = 'Insert'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Pos', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'Value', - dataType: 'Data', - defValue: 1, - pinInId: null, - }, - input2: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 4; - nodeDescription.colums = 10; - } - if (type == 'PushBack') { - nodeDescription.nodeTitle = 'PushBack'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Data', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'PushFront') { - nodeDescription.nodeTitle = 'PushFront'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Data', - defValue: 1, - pinInId: null, - }, - input1: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'PopBack') { - nodeDescription.nodeTitle = 'PopBack'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'PopFront') { - nodeDescription.nodeTitle = 'PopFront'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 10; - } - if (type == 'Search') { - nodeDescription.nodeTitle = 'Search'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Data', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Exist', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - }, - output1: { - outputTitle: 'Index', - dataType: 'Number', - pinOutId: null, - outOrder: 1, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 11; - } - if (type == 'BinarySearch(Num)') { - nodeDescription.nodeTitle = 'BinarySearch(Num)'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Value', - dataType: 'Number', - defValue: 0, - pinInId: null, - }, - input1: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Exist', - dataType: 'Boolean', - pinOutId: null, - outOrder: 0, - - }, - output1: { - outputTitle: 'Lower Bound', - dataType: 'Number', - pinOutId: null, - outOrder: 1, - }, - output2: { - outputTitle: 'Upper Bound', - dataType: 'Number', - pinOutId: null, - outOrder: 2, - } - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 13; - } - if (type == 'Max(Array)') { - nodeDescription.nodeTitle = 'Max(Array)'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'MaxValue', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - }, - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 11; - } - if (type == 'Min(Array)') { - nodeDescription.nodeTitle = 'Min(Array)'; - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'MinValue', - dataType: 'Number', - pinOutId: null, - outOrder: 0, - }, - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 11; - } - if (type == 'HttpRequest') { - nodeDescription.nodeTitle = 'HttpRequest'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: 'OnSuccess', - pinExecOutId: null, - outOrder: 0, - }, - execOut1: { - execOutTitle: 'OnFail', - pinExecOutId: null, - outOrder: 2, - }, - execOut2: { - execOutTitle: 'Continue', - pinExecOutId: null, - outOrder: 3, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'URL', - dataType: 'String', - defValue: "'link'", - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'JSON', - dataType: 'Data', - pinOutId: null, - outOrder: 1, - }, - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == 'OpenWindow') { - nodeDescription.nodeTitle = 'OpenWindow'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'URL', - dataType: 'String', - defValue: "'link'", - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Success?', - dataType: 'Boolean', - pinOutId: null, - outOrder: 1, - }, - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == 'GetByName(JSON)') { - nodeDescription.nodeTitle = 'GetByName(JSON)'; - nodeDescription.inputs = { - input0: { - inputTitle: 'JSON', - dataType: 'Data', - isInputBoxRequired: false, - pinInId: null, - }, - input1: { - inputTitle: 'Name', - dataType: 'String', - defValue: "'id'", - pinInId: null, - } - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Data', - dataType: 'Data', - pinOutId: null, - outOrder: 0, - }, - } - nodeDescription.color = 'Get'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == 'StrToArray') { - nodeDescription.nodeTitle = 'StrToArray'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'String', - dataType: 'String', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'Array', - dataType: 'Array', - pinOutId: null, - outOrder: 1, - }, - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - if (type == 'ArrayToStr') { - nodeDescription.nodeTitle = 'ArrayToStr'; - nodeDescription.execIn = true; - nodeDescription.pinExecInId = null; - nodeDescription.execOut = { - execOut0: { - execOutTitle: null, - pinExecOutId: null, - outOrder: 0, - }, - } - nodeDescription.inputs = { - input0: { - inputTitle: 'Array', - dataType: 'Array', - isInputBoxRequired: false, - pinInId: null, - }, - } - nodeDescription.outputs = { - output0: { - outputTitle: 'String', - dataType: 'String', - pinOutId: null, - outOrder: 1, - }, - } - nodeDescription.color = 'Func'; - nodeDescription.rows = 2; - nodeDescription.colums = 12; - } - - new this.ProgramNode(nodeDescription, location, layer, stage); - } - - -} - - - -/* - -//required json -{ - type: string, - id: num, - inputs:{ - count: integer, - execIn1:{ - name: "", - wire: KonvaWire else null - } - ip1: { - dataType: string, - default: num/str etc, - value: num/str etc, - name: "" - wire: Konva.Line else null if no wire - } - } - outputs:{ - count: integer, - execOut1:{ - name: "", - wire: KonvaWire else null - } - out1: { - dataType: string, - default: num/str etc, - value: num/str etc, - name: "" - wire: Konva.Line else null if no wire - } - } - -} - - -*/ \ No newline at end of file diff --git a/javascript/SaveAndLoad/SaveAndLoad.js b/javascript/SaveAndLoad/SaveAndLoad.js deleted file mode 100644 index cc39ce7..0000000 --- a/javascript/SaveAndLoad/SaveAndLoad.js +++ /dev/null @@ -1,207 +0,0 @@ -import { Nodes } from '../Nodes/nodes.js' -import { addConnectionWire } from '../Wiring/Wiring.js' -import { variableList } from '../Variable/variable.js' -import {showAlert, vscriptOnLoad} from '../main/alertBox.js' -function writeError(err, msg) { - document.getElementById("console-window").classList.toggle("hidden", false); - let codeDoc = document.getElementById("console").contentWindow.document; - codeDoc.open(); - codeDoc.writeln( - `\n - - - - "${msg}"
- ${err} -
- - - ` - ); - codeDoc.close(); -} -let placeLocation = function (location) { - //"this" is stage - return { - x: (location.x - this.x()) / this.scaleX(), - y: (location.y - this.y()) / this.scaleY() - }; -} -export class Export { - constructor(stage, layer, wireLayer) { - document.getElementById('export').addEventListener("click", (e) => { - let exportScript = []; - let nodesData = []; - let wireData = []; - layer.find('.aProgramNodeGroup').forEach((node, index) => { - if (node.name() == 'aProgramNodeGroup') { - let nodeData = { - position: node.position(), - nodeDescription: node.customClass.nodeDescription, - }; - nodesData.push(nodeData); - } - }); - wireLayer.find('.isConnection').forEach((aWire, index) => { - if (aWire.name() == 'isConnection') { - let wireD = { - srcId: aWire.attrs.src.id(), - destId: aWire.attrs.dest.id(), - } - wireData.push(wireD); - } - }) - exportScript = { - variables: variableList.variables, - nodesData: nodesData, - wireData: wireData, - } - let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportScript)); - let exportAnchorElem = document.getElementById('exportAnchorElem'); - exportAnchorElem.setAttribute("href", dataStr); - exportAnchorElem.setAttribute("download", "wireScript.json"); - exportAnchorElem.click(); - - // console.log(JSON.stringify(exportScript)); - // console.log(JSON.parse(JSON.stringify(exportScript))); - }); - } -} -export function refresh(layer, wireLayer) { - layer.destroyChildren(); - wireLayer.destroyChildren(); - variableList.deleteAllVariables(); - layer.draw(); - wireLayer.draw(); -} - -export class Import { - constructor(stage, layer, wireLayer, script) { - refresh(layer, wireLayer); - let json = null; - try { - json = JSON.parse(script); - } - catch (err) { - writeError(err, "Error In Loading JSON(JSON TEMPERED)"); - } - // console.log(json); - printContent(json, stage, layer, wireLayer); - } -} -export class Save { - constructor(stage, layer, wireLayer) { - document.getElementById('save').addEventListener("click", (e) => { - let exportScript = []; - let nodesData = []; - let wireData = []; - layer.find('.aProgramNodeGroup').forEach((node, index) => { - if (node.name() == 'aProgramNodeGroup') { - let nodeData = { - position: node.position(), - nodeDescription: node.customClass.nodeDescription, - }; - nodesData.push(nodeData); - } - }); - wireLayer.find('.isConnection').forEach((aWire, index) => { - if (aWire.name() == 'isConnection') { - let wireD = { - srcId: aWire.attrs.src.id(), - destId: aWire.attrs.dest.id(), - } - wireData.push(wireD); - } - }) - exportScript = { - variables: variableList.variables, - nodesData: nodesData, - wireData: wireData, - } - localStorage.setItem('lastLoadWireScriptJSON', JSON.stringify(exportScript)); - let savingWindow = document.getElementById("saving"); - // let importMenu = document.getElementById("import-menu"); - [...document.getElementsByClassName("sidebox")].forEach(value => { - if (value !== savingWindow) { - value.classList.toggle("hidden", true); - } - else { - value.classList.toggle("hidden", false); - } - }) - setTimeout(() => { - savingWindow.classList.toggle("hidden", true); - }, 600); - - }); - window.addEventListener("load", () => { - // console.log("loaded"); - prompLastSave(stage, layer, wireLayer); - }) - } -} - -export function prompLastSave(stage, layer, wireLayer) { - let saveMenu = document.getElementById("save-menu"); - [...document.getElementsByClassName("sidebox")].forEach(value => { - value.classList.toggle("hidden", true); - }); - // document.getElementById("saving").classList.toggle("hidden", true); - // document.getElementById("import-menu").classList.toggle("hidden", true); - if (localStorage.getItem('lastLoadWireScriptJSON') && localStorage.getItem('lastLoadWireScriptJSON') != "{\"variables\":[],\"nodesData\":[],\"wireData\":[]}") { - saveMenu.classList.toggle("hidden", false); - document.getElementById("load-btn").onclick = function () { - new Import(stage, layer, wireLayer, localStorage.getItem('lastLoadWireScriptJSON')); - saveMenu.classList.toggle("hidden", true); - }; - document.getElementById("load-cancel-btn").onclick = function () { - saveMenu.classList.toggle("hidden", true); - }; - } - else{ - vscriptOnLoad(stage); - showAlert('No Previous Save Was Found'); - } -} - -function printContent(json, stage, layer, wireLayer) { - for (let aNode of json.nodesData) { - try { - new Nodes.ProgramNode(aNode.nodeDescription, { x: aNode.position.x * stage.scaleX() + stage.x(), y: aNode.position.y * stage.scaleY() + stage.y() }, layer, stage); - - } - catch (err) { - writeError(err, "Error Occurred In Importing The JSON(Node Description Not Valid)"); - } - } - // let X = layer.findOne('Group'); - // console.log(layer.children); - // console.log(X); - for (let aWire of json.wireData) { - // console.log(`${aWire.srcId}`, `${aWire.destId}`); - let src = layer.findOne(`#${aWire.srcId}`); - let dest = layer.findOne(`#${aWire.destId}`); - // console.log(src, dest); - try { - addConnectionWire(dest, src, stage, 1, wireLayer); - } - catch (err) { - writeError(err, "Error Occurred In Importing The JSON(Wire Data Not Valid)"); - } - } - for (let aVariable of json.variables) { - try { - variableList.addVariable(aVariable); - } - catch (err) { - writeError(err, "Error Occurred In Importing The JSON(Variable Data Not Valid)"); - } - } - layer.draw(); - wireLayer.draw(); -} diff --git a/javascript/Variable/variable.js b/javascript/Variable/variable.js deleted file mode 100644 index f9083b9..0000000 --- a/javascript/Variable/variable.js +++ /dev/null @@ -1,82 +0,0 @@ -import {colorMap} from '../ColorMap/colorMap.js' -import {ContextMenu } from '../ContextMenu/contextMenu.js' -class VariableList { - - constructor() - { - this.variables = []; - this.variablesElements = []; - } - makeContextMenuItem(variable, setOrGet){ - let div = document.createElement("div"); - div.classList.toggle("context-menu-items", true); - div.setAttribute('data-datatype', `${variable.dataType}`); - // div.id=`${variable.dataType}-${variable.name}-${setOrGet}`; - div.innerHTML = `${(setOrGet == 'set') ? 'Set': 'Get'} ${variable.name}`; - return div; - } - addVariable(variable) - { - // this.variables[variableName] = { - // name: variableName, - // dataType: document.getElementById("variable-data-type").value, - // value: value, - // }; - this.variables.push(variable); - let el = this.makeLeftPanelVariableListItem(variable); - // - document.getElementById("variable-list").appendChild(el); - //
GetRandom
- // document.getElementById("context-menu").innerHTML += `
Set ${variable.name}
`; - // document.getElementById("context-menu").innerHTML += `
Get ${variable.name}
`; - let set = this.makeContextMenuItem(variable, 'set'); - let get = this.makeContextMenuItem(variable, 'get'); - // console.log(set, get); - document.getElementById("context-menu").appendChild(get); - document.getElementById("context-menu").appendChild(set); - ContextMenu.addEventToCtxMenuItems(set); - ContextMenu.addEventToCtxMenuItems(get); - this.variablesElements.push(set); - this.variablesElements.push(get) - } - makeLeftPanelVariableListItem(variable) { - let li = document.createElement('li'); - li.id = `${variable.dataType}-${variable.name}`; - li.classList.toggle('list-group-item', true); - li.classList.toggle('left-panel-variable', true); - li.style.width = "100%"; - li.style.borderWidth = `2px 2px 2px 2px`; - li.style.borderStyle = 'solid'; - li.style.margin = '1rem'; - li.style.boxShadow = `inset 0px 0px 5px ${colorMap[variable.dataType]}`; - li.style.backgroundColor = `transparent`; - li.style.borderColor = `${colorMap[variable.dataType]}`; - li.setAttribute("draggable", "true"); - let text = document.createTextNode(`${variable.name}`); - li.appendChild(text); - li.addEventListener('mouseover', (e) => { - li.style.boxShadow = `inset 0px 0px 30px ${colorMap[variable.dataType]}`; - }); - li.addEventListener('mouseleave', (e) => { - li.style.boxShadow = `inset 0px 0px 5px ${colorMap[variable.dataType]}`; - }); - li.addEventListener("dragstart", (e) =>{ - e.dataTransfer.setData("variableName", `${variable.name}`); - e.dataTransfer.setData("dataType", `${variable.dataType}`); - }); - return li; - // return `
  • ${variable.name} - //
  • `; - } - - deleteAllVariables() - { - this.variables = []; - document.getElementById("variable-list").innerHTML = ''; - this.variablesElements.forEach((elem, index) => { - elem.remove(); - }) - } -} - -export var variableList = new VariableList(); \ No newline at end of file diff --git a/javascript/VisualScriptToJavascript/VisualScriptToJavascript.js b/javascript/VisualScriptToJavascript/VisualScriptToJavascript.js deleted file mode 100644 index 13af7e7..0000000 --- a/javascript/VisualScriptToJavascript/VisualScriptToJavascript.js +++ /dev/null @@ -1,578 +0,0 @@ -import { variableList } from '../Variable/variable.js' -import { showAlert } from '../main/alertBox.js' -import { BuilInFunctions } from './builtInFunctions.js' -export var VSToJS = class { - - constructor(stage, layer, isRunOrCode) { - this.script = ''; - this.builtin_functions = {}; - this.nodeCount = 0; - this.isRunOrCode = isRunOrCode; - for (let variable of variableList.variables) { - // console.log(variable); - this.script += `let ${variable.name} = ${variable.value};\n`; - } - let begin = this.getBegin(stage); - if (begin) { - try { - this.coreAlgorithm(begin); - // console.log(this.script); - if (this.isRunOrCode == "Run") { - document.getElementById("console-window").classList.toggle("hidden", false); - let codeDoc = document.getElementById("console").contentWindow.document; - // console.log("run"); - codeDoc.open(); - codeDoc.writeln( - `\n - - -

    - - - - ` - ); - codeDoc.close(); - } - } - catch (err) { - document.getElementById("console-window").classList.toggle("hidden", false); - let codeDoc = document.getElementById("console").contentWindow.document; - this.script = ''; - this.builtin_functions = {}; - codeDoc.open(); - codeDoc.writeln( - `\n - - - - Recheck the nodes
    - ${err.name === 'RangeError' ? 'CyclicDependence : Irresolvable Cycle(s) Exists' : `UnknownException: Improve The Editor By Opening Issue On GitHub(Just Attach The Exported Graph)`} -
    - - - ` - ); - } - } - } - getBegin(stage) { - let X = stage.find("#Begin"); - if (X.length == 0) { - showAlert("Include Begin Node"); - } - else if (X.length > 1) { - showAlert("Multiple Begin Nodes"); - } - else return X[0]; - } - getExecOut(node) { - let X = []; - for (let aNode of node.customClass.execOutPins) { - if (aNode.wire) - X.push(aNode.wire.attrs.dest.getParent()); - else - X.push(null); - } - // console.log(X); - return X; - } - getSrcOutputPinNumber(grp, aNodeWire) { - let c = 0; - for (let eachPin of grp.customClass.outputPins) { - for (let aWire of eachPin.wire) { - if (aWire === aNodeWire) { - return c; - } - } - c++; - } - } - getInputPins(node) { - let X = []; - for (let aNode of node.customClass.inputPins) { - if (aNode.wire) { - X.push({ node: aNode.wire.attrs.src.getParent(), isWire: true, srcOutputPinNumber: this.getSrcOutputPinNumber(aNode.wire.attrs.src.getParent(), aNode.wire) }); - } - else { - // console.log(aNode.textBox); - X.push({ node: aNode.textBox.textBox.text(), isWire: false, srcOutputPinNumber: null }); - } - } - return X; - } - coreAlgorithm(node) { - if (node == null) return; - let execOutPins = this.getExecOut(node); - let inputPins = this.getInputPins(node); - // console.log(node.customClass.type); - // console.log(inputPins); - if (node.customClass.type.isGetSet) { - if (node.customClass.type.typeOfNode.slice(0, 3) == 'Set') { - this.script += `${node.customClass.type.typeOfNode.slice(4)} = ${this.handleInputs(inputPins[0])};\n`; - for (let each of execOutPins) { - this.coreAlgorithm(each); - } - } - } - else { - switch (node.customClass.type.typeOfNode) { - case "Begin": { - this.coreAlgorithm(execOutPins[0]); - let func_string = `/////////CodeWire Functions Space Begins///////////// - - `; - for (let each_function in this.builtin_functions) { - func_string = func_string + BuilInFunctions[each_function]; - } - func_string += ` - /////////CodeWire Functions Space Ends///////////// - //\n//\n/////////Generated JS Code Space Begins///////////// - `; - this.script = func_string + this.script; - this.script += `\n/////////Generated JS Code Space Ends/////////////`; - } - break; - case "Print": { - this.script += `console.log(${this.handleInputs(inputPins[0])});\n - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "Alert": { - this.script += `alert(${this.handleInputs(inputPins[0])});\n - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "Confirm": { - this.builtin_functions = { ...this.builtin_functions, _confirm: true }; - this.script += `let _confirm_answer${node._id} = _confirm(${this.handleInputs(inputPins[0])});\n - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "Prompt": { - this.builtin_functions = { ...this.builtin_functions, _prompt: true }; - this.script += `let [_prompt_ok${node._id}, _prompt_value${node._id}] = _prompt(${this.handleInputs(inputPins[0])}, ${this.handleInputs(inputPins[1])});\n - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "OpenWindow": { - this.builtin_functions = { ...this.builtin_functions, _newWindow: true }; - this.script += `let _window_opened${node._id} = _newWindow(${this.handleInputs(inputPins[0])});\n - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "If/Else": { - this.script += `if(${this.handleInputs(inputPins[0])}){\n`; - this.coreAlgorithm(execOutPins[0]); - this.script += `}\n`; - this.script += `else{\n`; - this.coreAlgorithm(execOutPins[1]); - this.script += `}\n`; - this.coreAlgorithm(execOutPins[2]); - } - break; - case "ForLoop": { - let forVar = `i${node._id}`; //variable used inside the for loop - this.script += `for(let ${forVar} = (${this.handleInputs(inputPins[0])}); ${forVar} < (${this.handleInputs(inputPins[1])}); ${forVar} += (${this.handleInputs(inputPins[2])})){\n`; - this.coreAlgorithm(execOutPins[0]); - this.script += `}\n`; - this.coreAlgorithm(execOutPins[1]); - } - break; - case "ForEachLoop": { - let forVar = `i${node._id}`; //variable used inside the for loop - this.script += `${this.handleInputs(inputPins[0])}.forEach((value${forVar}, ${forVar}, array${forVar}) => {\n`; - this.coreAlgorithm(execOutPins[0]); - this.script += `});\n`; - this.coreAlgorithm(execOutPins[1]); - } - break; - case "Break": { - this.script += `break;\n`; - } - break; - case "Continue": { - this.script += `continue;\n`; - } - break; - case "WhileLoop": { - this.script += ` while(${this.handleInputs(inputPins[0])}){\n`; - this.coreAlgorithm(execOutPins[0]); - this.script += `}\n`; - this.coreAlgorithm(execOutPins[1]); - } - break; - case "SetByPos": { - this.script += `${this.handleInputs(inputPins[2])}[${this.handleInputs(inputPins[0])}] = ${this.handleInputs(inputPins[1])};\n`; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "PushBack": { - this.script += `${this.handleInputs(inputPins[1])}.push(${this.handleInputs(inputPins[0])});\n`; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "PushFront": { - this.script += `${this.handleInputs(inputPins[1])}.unshift(${this.handleInputs(inputPins[0])});\n`; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "PopBack": { - this.script += `${this.handleInputs(inputPins[0])}.pop();\n`; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "PopFront": { - this.script += `${this.handleInputs(inputPins[0])}.shift();\n`; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "Insert": { - this.script += `${this.handleInputs(inputPins[2])}.splice(${this.handleInputs(inputPins[0])}, 0, ${this.handleInputs(inputPins[1])});\n`; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "Reverse": { - this.script += `${this.handleInputs(inputPins[0])}.reverse(); - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "Swap": { - this.script += ` - [${this.handleInputs(inputPins[0])}, ${this.handleInputs(inputPins[1])}] = [${this.handleInputs(inputPins[1])}, ${this.handleInputs(inputPins[0])}]; //swap using array destructuring :) \n`; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "Sort(Num)": { - this.script += ` - (${this.handleInputs(inputPins[1])}) ? ${this.handleInputs(inputPins[0])}.sort((a, b) => a-b) : ${this.handleInputs(inputPins[0])}.sort((a, b) => b-a);\n - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "HttpRequest": { - this.builtin_functions = { ...this.builtin_functions, fetch_data: true }; - this.script += ` - fetch_data(${this.handleInputs(inputPins[0])}) - .then((json_data${node._id}) => { - `; - this.coreAlgorithm(execOutPins[0]); - this.script += ` - }) - .catch((err) => { - ` - this.coreAlgorithm(execOutPins[1]); - this.script += ` - }); - `; - this.coreAlgorithm(execOutPins[2]); - } - break; - case "StrToArray": { - this.script += `let strArray${node._id} = ${this.handleInputs(inputPins[0])}.split(''); - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - case "ArrayToStr": { - this.script += `let arrayStr${node._id} = ${this.handleInputs(inputPins[0])}.join(''); - `; - this.coreAlgorithm(execOutPins[0]); - } - break; - } - } - } - handleInputs(inputNode) { - - if (!inputNode.isWire) { - return inputNode.node; - } - let inputPins = this.getInputPins(inputNode.node); - if (inputNode.node.customClass.type.isGetSet) { - return `${inputNode.node.customClass.type.typeOfNode.slice(4)}`; - } - // if (inputNode.node.customClass.type.isFor) { - // return `(i${inputNode.node.customClass.type.isFor})`; - // } - let expr = ``; - switch (inputNode.node.customClass.type.typeOfNode) { - case "Add": { - expr = `(${this.handleInputs(inputPins[0])} + ${this.handleInputs(inputPins[1])})`; - } - break; - case "Subtract": { - expr = `(${this.handleInputs(inputPins[0])} - ${this.handleInputs(inputPins[1])})`; - } - break; - case "Multiply": { - expr = `(${this.handleInputs(inputPins[0])} * ${this.handleInputs(inputPins[1])})`; - } - break; - case "Divide": { - expr = `(${this.handleInputs(inputPins[0])} / ${this.handleInputs(inputPins[1])})`; - } - break; - case "Power": { - expr = `Math.pow(${this.handleInputs(inputPins[0])}, ${this.handleInputs(inputPins[1])})`; - } - break; - case "Modulo": { - expr = `(${this.handleInputs(inputPins[0])} % ${this.handleInputs(inputPins[1])})`; - } - break; - case "AND": { - expr = `(${this.handleInputs(inputPins[0])} && ${this.handleInputs(inputPins[1])})`; - } - break; - case "Ceil": { - expr = `Math.ceil(${this.handleInputs(inputPins[0])})`; - } - break; - case "Floor": { - expr = `Math.floor(${this.handleInputs(inputPins[0])})`; - } - break; - case "OR": { - expr = `(${this.handleInputs(inputPins[0])} || ${this.handleInputs(inputPins[1])})`; - } - break; - case "XOR": { - expr = `(${this.handleInputs(inputPins[0])} ^ ${this.handleInputs(inputPins[1])})`; - } - break; - case "NEG": { - expr = `!(${this.handleInputs(inputPins[0])})`; - } - break; - case "bAND": { - expr = `(${this.handleInputs(inputPins[0])} & ${this.handleInputs(inputPins[1])})`; - } - break; - case "bOR": { - expr = `(${this.handleInputs(inputPins[0])} | ${this.handleInputs(inputPins[1])})`; - } - break; - case "bXOR": { - expr = `(${this.handleInputs(inputPins[0])} ^ ${this.handleInputs(inputPins[1])})`; - } - break; - case "bNEG": { - expr = `~${this.handleInputs(inputPins[0])}`; - } - break; - case "Random": { - expr = `Math.random()`; - } - break; - case "Equals": { - expr = `(${this.handleInputs(inputPins[0])} === ${this.handleInputs(inputPins[1])})`; - } - break; - case "Not Equals": { - expr = `(${this.handleInputs(inputPins[0])} !== ${this.handleInputs(inputPins[1])})`; - } - break; - case "LessEq": { - expr = `(${this.handleInputs(inputPins[0])} <= ${this.handleInputs(inputPins[1])})`; - } - break; - case "Less": { - expr = `(${this.handleInputs(inputPins[0])} < ${this.handleInputs(inputPins[1])})`; - } - break; - case "Greater": { - expr = `(${this.handleInputs(inputPins[0])} > ${this.handleInputs(inputPins[1])})`; - } - break; - case "GreaterEq": { - expr = `(${this.handleInputs(inputPins[0])} >= ${this.handleInputs(inputPins[1])})`; - } - break; - case "Length": { - expr = `${this.handleInputs(inputPins[0])}.length`; - } - break; - case "GetByPos": { - // console.log("GetByPos"); - expr = `${this.handleInputs(inputPins[1])}[${this.handleInputs(inputPins[0])}]`; - } - break; - case "SetByPos": { - expr = `${this.handleInputs(inputPins[2])}[${this.handleInputs(inputPins[0])}]`; - } - break; - case "isEmpty": { - expr = `(${this.handleInputs(inputPins[0])}.length == (0))`; - } - break; - case "Reverse": { - expr = `${this.handleInputs(inputPins[0])}`; - } - break; - case "PushBack": { - expr = `${this.handleInputs(inputPins[1])}`; - } - break; - case "PushFront": { - expr = `${this.handleInputs(inputPins[1])}`; - } - break; - case "PopBack": { - expr = `${this.handleInputs(inputPins[0])}`; - } - break; - case "PopFront": { - expr = `${this.handleInputs(inputPins[0])}`; - } - break; - case "Front": { - expr = `${this.handleInputs(inputPins[0])}[0]`; - } - break; - case "Back": { - expr = `${this.handleInputs(inputPins[0])}[${this.handleInputs(inputPins[0])}.length - 1]`; - } - break; - case "Insert": { - expr = `${this.handleInputs(inputPins[2])}`; - } - break; - case "Swap": { - expr = `${this.handleInputs(inputPins[inputNode.srcOutputPinNumber])}`; - } - break; - case "ForLoop": { - expr = `i${inputNode.node._id}`; - } - break; - case "ForEachLoop": { - expr = ``; - if (inputNode.srcOutputPinNumber == 0) - expr = `valuei${inputNode.node._id}`; - else if (inputNode.srcOutputPinNumber == 1) - expr = `i${inputNode.node._id}`; - else - expr = `arrayi${inputNode.node._id}`; - } - break; - case "Sort(Num)": { - expr = `${this.handleInputs(inputPins[0])}`; - } - break; - case "Max(Array)": { - expr = `Math.max(...${this.handleInputs(inputPins[0])})`; - } - break; - case "Min(Array)": { - expr = `Math.min(...${this.handleInputs(inputPins[0])})`; - } - break; - case "Max(Num)": { - expr = `Math.max(${this.handleInputs(inputPins[0])}, ${this.handleInputs(inputPins[1])})`; - } - break; - case "Min(Num)": { - expr = `Math.min(${this.handleInputs(inputPins[0])}, ${this.handleInputs(inputPins[1])})`; - } - break; - case "Search": { - expr = ``; - if (inputNode.srcOutputPinNumber == 0) { - expr = `(${this.handleInputs(inputPins[1])}.find((value) => value === ${this.handleInputs(inputPins[0])}) === ${this.handleInputs(inputPins[0])})`; - } - else { - expr = `(${this.handleInputs(inputPins[1])}.findIndex((value) => value === ${this.handleInputs(inputPins[0])}))`; - } - } - break; - case "BinarySearch(Num)": { - expr = ``; - if (inputNode.srcOutputPinNumber == 0) { - this.builtin_functions = { ...this.builtin_functions, binary_search_exist: true }; - expr = `binary_search_exist(${this.handleInputs(inputPins[1])}, ${this.handleInputs(inputPins[0])})`; - } - else if (inputNode.srcOutputPinNumber == 1) { - this.builtin_functions = { ...this.builtin_functions, lower_bound: true }; - expr = `lower_bound(${this.handleInputs(inputPins[1])}, ${this.handleInputs(inputPins[0])})`; - } - else { - this.builtin_functions = { ...this.builtin_functions, upper_bound: true }; - expr = `upper_bound(${this.handleInputs(inputPins[1])}, ${this.handleInputs(inputPins[0])})`; - } - } - break; - case "HttpRequest": { - expr = `json_data${inputNode.node._id}`; - } - break; - case "GetByName(JSON)": { - expr = `${this.handleInputs(inputPins[0])}[${this.handleInputs(inputPins[1])}]`; - } - break; - case "Confirm": { - expr = `_confirm_answer${inputNode.node._id}`; - } - break; - case "OpenWindow": { - expr = `_window_opened${inputNode.node._id}`; - } - break; - case "Prompt": { - expr = ``; - if (inputNode.srcOutputPinNumber == 0) { - expr = `_prompt_ok${inputNode.node._id}`; - } - else { - expr = `_prompt_value${inputNode.node._id}`; - } - } - break; - case "StrToArray": { - expr = `strArray${inputNode.node._id}`; - } - break; - case "ArrayToStr": { - expr = `arrayStr${inputNode.node._id}`; - } - break; - } - return expr; - } - - - -}; \ No newline at end of file diff --git a/javascript/main/alertBox.js b/javascript/main/alertBox.js deleted file mode 100644 index eb6df97..0000000 --- a/javascript/main/alertBox.js +++ /dev/null @@ -1,77 +0,0 @@ -import { refresh } from '../SaveAndLoad/SaveAndLoad.js' -import { Import } from '../SaveAndLoad/SaveAndLoad.js' - -export function showAlert(msg) { - let alertMsg = document.getElementById("alert-box").children[0].children[0]; - let alertBox = document.getElementById("alert-box"); - document.getElementById("alert-ok-btn").addEventListener("click", (e) => { - alertBox.classList.toggle("hidden", true); - // console.log("ok clicked"); - }); - alertMsg.innerHTML = `Alert: ${msg}`; - alertBox.classList.toggle('hidden', false); - [...document.getElementsByClassName("sidebox")].forEach(value => { - if (value !== alertBox) { - value.classList.toggle("hidden", true); - } - else { - value.classList.toggle("hidden", false); - } - }) -} - -//
    Alert: Current Scipt Will Be Lost Unless Exported
    - -export function prompRefreshOrStarter(type, stage) { - let refreshBox = document.getElementById("refresh-box"); - let refBtn = document.getElementById("refresh-btn"); - let refCnclBtn = document.getElementById("refresh-cancel-btn"); - // console.log("refresh clicked"); - if (type == 'refresh') { - refreshBox.children[0].children[1].innerHTML = 'Refresh' - refreshBox.classList.toggle('hidden', false); - [...document.getElementsByClassName("sidebox")].forEach(value => { - if (value !== refreshBox) { - value.classList.toggle("hidden", true); - } - else { - value.classList.toggle("hidden", false); - } - }); - refBtn.addEventListener("click", (e) => { - refresh(stage.findOne("#main_layer"), stage.findOne("#wireLayer")); - refreshBox.classList.toggle('hidden', true); - }); - refCnclBtn.addEventListener("click", (e) => { - refreshBox.classList.toggle('hidden', true); - }); - } - if (type == 'starter') { - refreshBox.children[0].children[1].innerHTML = 'Load'; - refreshBox.classList.toggle('hidden', false); - [...document.getElementsByClassName("sidebox")].forEach(value => { - if (value !== refreshBox) { - value.classList.toggle("hidden", true); - } - else { - value.classList.toggle("hidden", false); - } - }); - refBtn.addEventListener("click", (e) => { - refreshBox.classList.toggle('hidden', true); - vscriptOnLoad(stage); - }); - refCnclBtn.addEventListener("click", (e) => { - refreshBox.classList.toggle('hidden', true); - }) ; - } -} -export function vscriptOnLoad(stage) { - // stage.setScale({x: 0.5, y: 0.5}); - // const starterFile = { "variables": [{ "name": "coolStuff", "dataType": "String", "value": "'https://www.youtube.com/watch?v=dQw4w9WgXcQ'" }], "nodesData": [{ "position": { "x": 285.76949478124993, "y": 160.00829895214838 }, "nodeDescription": { "nodeTitle": "Begin", "execIn": false, "pinExecInId": null, "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "10", "outOrder": 0 } }, "color": "Begin", "rows": 2, "colums": 10 } }, { "position": { "x": 501.3872296698097, "y": 161.2761797590843 }, "nodeDescription": { "nodeTitle": "Confirm", "execIn": true, "pinExecInId": "62", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "63", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Message", "dataType": "String", "defValue": "'u wanna see some cool stuff ?'", "pinInId": "64" } }, "outputs": { "output0": { "outputTitle": "Ok?", "dataType": "Boolean", "pinOutId": "69", "outOrder": 1 } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 1216.4116108593755, "y": 150.01528070898422 }, "nodeDescription": { "nodeTitle": "OpenWindow", "execIn": true, "pinExecInId": "16", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "17", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "URL", "dataType": "String", "defValue": "'link'", "pinInId": "18" } }, "outputs": { "output0": { "outputTitle": "Success?", "dataType": "Boolean", "pinOutId": "23", "outOrder": 1 } }, "color": "Func", "rows": 2, "colums": 12 } }, { "position": { "x": 1203.519704023151, "y": 382.23448287513907 }, "nodeDescription": { "nodeTitle": "Alert", "execIn": true, "pinExecInId": "101", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "102", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Data", "defValue": "'u missed some nice stuff'", "pinInId": "103" } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 1202.448197106272, "y": 520.6776970135538 }, "nodeDescription": { "nodeTitle": "Alert", "execIn": true, "pinExecInId": "115", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "116", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Data", "defValue": "'After closing this dialog, right click on the editor to get bunch of new nodes and create your own scripts :)'", "pinInId": "117" } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 773.9416135561129, "y": 211.52728595257065 }, "nodeDescription": { "nodeTitle": "If/Else", "execIn": true, "pinExecInId": "78", "execOut": { "execOut0": { "execOutTitle": "True", "pinExecOutId": "79", "outOrder": 0 }, "execOut1": { "execOutTitle": "False", "pinExecOutId": "81", "outOrder": 1 }, "execOut2": { "execOutTitle": "Done", "pinExecOutId": "83", "outOrder": 2 } }, "inputs": { "input0": { "inputTitle": "Bool", "dataType": "Boolean", "defValue": true, "pinInId": "85" } }, "color": "Logic", "rows": 3, "colums": 12 } }, { "position": { "x": 1162.8208273281257, "y": 280.7125038125001 }, "nodeDescription": { "nodeTitle": "Get coolStuff", "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "String", "pinOutId": "32", "outOrder": 0 } }, "color": "Get", "rows": 2, "colums": 10 } }], "wireData": [{ "srcId": "10", "destId": "62" }, { "srcId": "63", "destId": "78" }, { "srcId": "79", "destId": "16" }, { "srcId": "81", "destId": "101" }, { "srcId": "83", "destId": "115" }, { "srcId": "69", "destId": "85" }, { "srcId": "32", "destId": "18" }] }; - const starterFile = { "variables": [{ "name": "coolStuff", "dataType": "String", "value": "'https://www.youtube.com/watch?v=hOHKltAiKXQ'" }, { "name": "fib0", "dataType": "Number", "value": "0" }, { "name": "fib1", "dataType": "Number", "value": "1" }, { "name": "tmp", "dataType": "Number", "value": "0" }, { "name": "fibArray", "dataType": "Array", "value": "[]" }, { "name": "catFactsApi", "dataType": "String", "value": "'https://catfact.ninja/fact'" }], "nodesData": [{ "position": { "x": 640.1957355310791, "y": 1149.6123184434612 }, "nodeDescription": { "nodeTitle": "Confirm", "execIn": true, "pinExecInId": "62", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "63", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Message", "dataType": "String", "defValue": "'u wanna see some cool stuff ?'", "pinInId": "64" } }, "outputs": { "output0": { "outputTitle": "Ok?", "dataType": "Boolean", "pinOutId": "69", "outOrder": 1 } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 943.0808918774987, "y": 1154.2878748323233 }, "nodeDescription": { "nodeTitle": "If/Else", "execIn": true, "pinExecInId": "78", "execOut": { "execOut0": { "execOutTitle": "True", "pinExecOutId": "79", "outOrder": 0 }, "execOut1": { "execOutTitle": "False", "pinExecOutId": "81", "outOrder": 1 }, "execOut2": { "execOutTitle": "Done", "pinExecOutId": "83", "outOrder": 2 } }, "inputs": { "input0": { "inputTitle": "Bool", "dataType": "Boolean", "defValue": true, "pinInId": "85" } }, "color": "Logic", "rows": 3, "colums": 12 } }, { "position": { "x": 624.8490952267291, "y": 76.13062296509307 }, "nodeDescription": { "nodeTitle": "ForLoop", "pinExecInId": "168", "execIn": true, "execOut": { "execOut0": { "execOutTitle": "Loop Body", "pinExecOutId": "169", "outOrder": 0 }, "execOut1": { "execOutTitle": "Completed", "pinExecOutId": "171", "outOrder": 2 } }, "inputs": { "input0": { "inputTitle": "From", "dataType": "Number", "defValue": 0, "pinInId": "173" }, "input1": { "inputTitle": "To(Excl)", "dataType": "Number", "defValue": 10, "pinInId": "178" }, "input2": { "inputTitle": "Increment", "dataType": "Number", "defValue": 1, "pinInId": "183" } }, "outputs": { "output0": { "outputTitle": "Index", "dataType": "Number", "pinOutId": "188", "outOrder": 1 } }, "color": "Logic", "rows": 2, "colums": 12 } }, { "position": { "x": 1062.358745795078, "y": 9.64809972519835 }, "nodeDescription": { "nodeTitle": "Print", "execIn": true, "pinExecInId": "197", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "198", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Data", "defValue": "'hello'", "pinInId": "199" } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 882.7650007538189, "y": 159.85163279515638 }, "nodeDescription": { "nodeTitle": "Get fib0", "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "Number", "pinOutId": "161", "outOrder": 0 } }, "color": "Get", "rows": 2, "colums": 10 } }, { "position": { "x": 1236.2691694516263, "y": 163.34281389620685 }, "nodeDescription": { "nodeTitle": "Add", "inputs": { "input0": { "inputTitle": "ValueA", "dataType": "Number", "defValue": 0, "pinInId": "240" }, "input1": { "inputTitle": "ValueB", "dataType": "Number", "defValue": 0, "pinInId": "245" } }, "outputs": { "output0": { "outputTitle": "Result", "dataType": "Number", "pinOutId": "250", "outOrder": 0 } }, "color": "Math", "rows": 2, "colums": 10 } }, { "position": { "x": 935.1522585885414, "y": 253.74975644182052 }, "nodeDescription": { "nodeTitle": "Get fib1", "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "Number", "pinOutId": "231", "outOrder": 0 } }, "color": "Get", "rows": 2, "colums": 10 } }, { "position": { "x": 1296.8417070314447, "y": 16.433630516210982 }, "nodeDescription": { "nodeTitle": "Set tmp", "execIn": true, "pinExecInId": "213", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "214", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Number", "defValue": 0, "pinInId": "215" } }, "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "Number", "pinOutId": "220", "outOrder": 1 } }, "color": "Func", "rows": 2, "colums": 12 } }, { "position": { "x": 1833.5365239733978, "y": 18.884241374940608 }, "nodeDescription": { "nodeTitle": "Set fib1", "execIn": true, "pinExecInId": "288", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "289", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Number", "defValue": 0, "pinInId": "290" } }, "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "Number", "pinOutId": "295", "outOrder": 1 } }, "color": "Func", "rows": 2, "colums": 12 } }, { "position": { "x": 1547.3197219286844, "y": -149.4043843715472 }, "nodeDescription": { "nodeTitle": "Set fib0", "execIn": true, "pinExecInId": "263", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "264", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Number", "defValue": 0, "pinInId": "265" } }, "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "Number", "pinOutId": "270", "outOrder": 1 } }, "color": "Func", "rows": 2, "colums": 12 } }, { "position": { "x": 360.3458171820312, "y": 74.17517317011703 }, "nodeDescription": { "nodeTitle": "Begin", "execIn": false, "pinExecInId": null, "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "10", "outOrder": 0 } }, "color": "Begin", "rows": 2, "colums": 10 } }, { "position": { "x": 1551.216765448137, "y": 5.548682440772666 }, "nodeDescription": { "nodeTitle": "Get fib1", "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "Number", "pinOutId": "279", "outOrder": 0 } }, "color": "Get", "rows": 2, "colums": 10 } }, { "position": { "x": 1512.771776695504, "y": 1416.9201863915812 }, "nodeDescription": { "nodeTitle": "Alert", "execIn": true, "pinExecInId": "115", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "116", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Data", "defValue": "'After closing this dialog, right click on the editor to get bunch of new nodes and create your own scripts :)'", "pinInId": "117" } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 1561.0501231968399, "y": 1251.0411440014332 }, "nodeDescription": { "nodeTitle": "Alert", "execIn": true, "pinExecInId": "101", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "102", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Data", "defValue": "'u missed some nice stuff'", "pinInId": "103" } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 1821.096442061649, "y": 1241.9167263753077 }, "nodeDescription": { "nodeTitle": "Get coolStuff", "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "String", "pinOutId": "32", "outOrder": 0 } }, "color": "Get", "rows": 2, "colums": 10 } }, { "position": { "x": 1902.6031409132486, "y": 1076.0798713887261 }, "nodeDescription": { "nodeTitle": "OpenWindow", "execIn": true, "pinExecInId": "16", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "17", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "URL", "dataType": "String", "defValue": "'link'", "pinInId": "18" } }, "outputs": { "output0": { "outputTitle": "Success?", "dataType": "Boolean", "pinOutId": "23", "outOrder": 1 } }, "color": "Func", "rows": 2, "colums": 12 } }, { "position": { "x": 1338.2444748633134, "y": 543.136740532748 }, "nodeDescription": { "nodeTitle": "Print", "execIn": true, "pinExecInId": "334", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "335", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Data", "defValue": "'hello'", "pinInId": "336" } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 669.8079691212556, "y": 560.617817270978 }, "nodeDescription": { "nodeTitle": "HttpRequest", "execIn": true, "pinExecInId": "306", "execOut": { "execOut0": { "execOutTitle": "OnSuccess", "pinExecOutId": "307", "outOrder": 0 }, "execOut1": { "execOutTitle": "OnFail", "pinExecOutId": "309", "outOrder": 2 }, "execOut2": { "execOutTitle": "Continue", "pinExecOutId": "311", "outOrder": 3 } }, "inputs": { "input0": { "inputTitle": "URL", "dataType": "String", "defValue": "'link'", "pinInId": "313" } }, "outputs": { "output0": { "outputTitle": "JSON", "dataType": "Data", "pinOutId": "318", "outOrder": 1 } }, "color": "Func", "rows": 2, "colums": 12 } }, { "position": { "x": 445.32242406433573, "y": 657.8024685377316 }, "nodeDescription": { "nodeTitle": "Get catFactsApi", "outputs": { "output0": { "outputTitle": "Value(Ref)", "dataType": "String", "pinOutId": "325", "outOrder": 0 } }, "color": "Get", "rows": 2, "colums": 10 } }, { "position": { "x": 1355.211745865666, "y": 786.6439536440772 }, "nodeDescription": { "nodeTitle": "Print", "execIn": true, "pinExecInId": "352", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "353", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Value", "dataType": "Data", "defValue": "'API REQUEST UNSUCCESSFUL'", "pinInId": "354" } }, "color": "Print", "rows": 3, "colums": 12 } }, { "position": { "x": 1096.6222103758923, "y": 617.8530961704762 }, "nodeDescription": { "nodeTitle": "GetByName(JSON)", "inputs": { "input0": { "inputTitle": "JSON", "dataType": "Data", "isInputBoxRequired": false, "pinInId": "368" }, "input1": { "inputTitle": "Name", "dataType": "String", "defValue": "'fact'", "pinInId": "370" } }, "outputs": { "output0": { "outputTitle": "Data", "dataType": "Data", "pinOutId": "375", "outOrder": 0 } }, "color": "Get", "rows": 2, "colums": 12 } }, { "position": { "x": 3719.492065534509, "y": 45.67011863451796 }, "nodeDescription": { "nodeTitle": "If/Else", "execIn": true, "pinExecInId": "407", "execOut": { "execOut0": { "execOutTitle": "True", "pinExecOutId": "408", "outOrder": 0 }, "execOut1": { "execOutTitle": "False", "pinExecOutId": "410", "outOrder": 1 }, "execOut2": { "execOutTitle": "Done", "pinExecOutId": "412", "outOrder": 2 } }, "inputs": { "input0": { "inputTitle": "Bool", "dataType": "Boolean", "defValue": true, "pinInId": "414" } }, "color": "Logic", "rows": 3, "colums": 12 } }, { "position": { "x": 3497.0899677450816, "y": 121.65556858361023 }, "nodeDescription": { "nodeTitle": "ForEachLoop", "pinExecInId": "442", "execIn": true, "execOut": { "execOut0": { "execOutTitle": "Loop Body", "pinExecOutId": "443", "outOrder": 0 }, "execOut1": { "execOutTitle": "Completed", "pinExecOutId": "445", "outOrder": 4 } }, "inputs": { "input0": { "inputTitle": "Array", "dataType": "Array", "isInputBoxRequired": false, "pinInId": "447" } }, "outputs": { "output0": { "outputTitle": "Value", "dataType": "Data", "pinOutId": "449", "outOrder": 1 }, "output1": { "outputTitle": "Index", "dataType": "Number", "pinOutId": "451", "outOrder": 2 }, "output2": { "outputTitle": "Array", "dataType": "Array", "pinOutId": "453", "outOrder": 3 } }, "color": "Logic", "rows": 2, "colums": 12 } }, { "position": { "x": 3755.240342745082, "y": 248.99431858361027 }, "nodeDescription": { "nodeTitle": "BinarySearch(Num)", "inputs": { "input0": { "inputTitle": "Value", "dataType": "Number", "defValue": 0, "pinInId": "424" }, "input1": { "inputTitle": "Array", "dataType": "Array", "isInputBoxRequired": false, "pinInId": "429" } }, "outputs": { "output0": { "outputTitle": "Exist", "dataType": "Boolean", "pinOutId": "431", "outOrder": 0 }, "output1": { "outputTitle": "Lower Bound", "dataType": "Number", "pinOutId": "433", "outOrder": 1 }, "output2": { "outputTitle": "Upper Bound", "dataType": "Number", "pinOutId": "435", "outOrder": 2 } }, "color": "Func", "rows": 2, "colums": 13 } }, { "position": { "x": 4023.809342745082, "y": 103.13356858361016 }, "nodeDescription": { "nodeTitle": "Continue", "execIn": true, "pinExecInId": "460", "color": "Logic", "rows": 2, "colums": 10 } }, { "position": { "x": 4030.755092745082, "y": 188.79781858361025 }, "nodeDescription": { "nodeTitle": "Break", "execIn": true, "pinExecInId": "466", "color": "Logic", "rows": 2, "colums": 10 } }, { "position": { "x": 4043.538201260707, "y": 288.14623690444165 }, "nodeDescription": { "nodeTitle": "Random", "outputs": { "output0": { "outputTitle": "Random[0,1)", "dataType": "Number", "pinOutId": "472", "outOrder": 0 } }, "color": "Math", "rows": 2, "colums": 10 } }, { "position": { "x": 4047.9482012607077, "y": 365.32127055007163 }, "nodeDescription": { "nodeTitle": "Swap", "execIn": true, "pinExecInId": "479", "execOut": { "execOut0": { "execOutTitle": null, "pinExecOutId": "480", "outOrder": 0 } }, "inputs": { "input0": { "inputTitle": "Ref1", "dataType": "Data", "isInputBoxRequired": false, "pinInId": "481" }, "input1": { "inputTitle": "Ref2", "dataType": "Data", "isInputBoxRequired": false, "pinInId": "483" } }, "outputs": { "output0": { "outputTitle": "Ref1", "dataType": "Data", "pinOutId": "485", "outOrder": 1 }, "output1": { "outputTitle": "Ref2", "dataType": "Data", "pinOutId": "487", "outOrder": 2 } }, "color": "Func", "rows": 2, "colums": 12 } }], "wireData": [{ "srcId": "10", "destId": "168" }, { "srcId": "63", "destId": "78" }, { "srcId": "69", "destId": "85" }, { "srcId": "169", "destId": "197" }, { "srcId": "161", "destId": "199" }, { "srcId": "161", "destId": "240" }, { "srcId": "231", "destId": "245" }, { "srcId": "198", "destId": "213" }, { "srcId": "250", "destId": "215" }, { "srcId": "220", "destId": "290" }, { "srcId": "214", "destId": "263" }, { "srcId": "264", "destId": "288" }, { "srcId": "279", "destId": "265" }, { "srcId": "83", "destId": "115" }, { "srcId": "81", "destId": "101" }, { "srcId": "79", "destId": "16" }, { "srcId": "32", "destId": "18" }, { "srcId": "307", "destId": "334" }, { "srcId": "325", "destId": "313" }, { "srcId": "309", "destId": "352" }, { "srcId": "318", "destId": "368" }, { "srcId": "375", "destId": "336" }] } - let starterJSON = JSON.stringify(starterFile); - new Import(stage, stage.findOne("#main_layer"), stage.findOne("#wireLayer"), starterJSON); -} - diff --git a/javascript/main/main.js b/javascript/main/main.js deleted file mode 100644 index ab5d8b7..0000000 --- a/javascript/main/main.js +++ /dev/null @@ -1,157 +0,0 @@ -import { AppStage } from '../AppStage/intiStage.js' -// import {SelectionBox} from '../SelectionBox/SelectionBox.js' -import { DragAndDrop } from '../DragAndDrop/DragAndDrop.js' -import { Wiring } from '../Wiring/Wiring.js' -import { ContextMenu } from '../ContextMenu/contextMenu.js' -import { leftPanel } from '../LeftPanel/LeftPanel.js' -import { VSToJS } from '../VisualScriptToJavascript/VisualScriptToJavascript.js' -import { Delete } from '../Delete/delete.js' -import { Export, Import, Save, prompLastSave } from '../SaveAndLoad/SaveAndLoad.js' -import { showAlert, prompRefreshOrStarter } from './alertBox.js' -import { refresh } from '../VisualScriptToJavascript/liveCode.js' -// var width = window.innerWidth; -// var height = window.innerHeight; -let stage = AppStage.getStage(document.getElementById("container").clientWidth, document.getElementById("container").clientHeight, 'container'); -var layer = new Konva.Layer({ - id: 'main_layer' -}); -let dragLayer = new Konva.Layer({ - id: 'dragLayer', -}); -stage.add(layer); -stage.add(dragLayer); -stage.container().style.backgroundPosition = `${stage.position().x} ${stage.position().y}`; - -// stage.on("wheel", () => { -// if (inputIsFocused) { -// document.getElementById("number-ip").blur(); -// document.getElementById("string-ip").blur(); -// document.getElementById("bool-ip").blur(); -// } -// }); -// SelectionBox.setSelectionBox(layer, stage); -Delete.enableDelete(stage, layer); -// DragAndDrop.DragAndDrop(stage, layer); -Wiring.enableWiring(stage, layer); -ContextMenu.contextMenu(stage, layer); -let panel = new leftPanel(); -// layer.toggleHitCanvas(); -// document.getElementById("number-ip").value = 12; -layer.draw(); -document.getElementById("Run").addEventListener("click", (e) => { - try { - let script = new VSToJS(stage, layer, "Run").script; - // let script = new VSToJS(stage, layer, "live-code-refresh").script; - refresh(script); - } - catch (err) { - - } -}); -// stage.on('mouseup', () => { -// console.log("x"); -// }) - -new Save(stage, layer, stage.findOne('#wireLayer')); -new Export(stage, layer, stage.findOne('#wireLayer')); -// let script = `{"variables":[{"name":"sadsad","dataType":"Number","value":"0"},{"name":"sadsaddd","dataType":"Array","value":"[1,2]"}],"nodesData":[{"position":{"x":511,"y":16},"nodeDescription":{"nodeTitle":"Begin","execIn":false,"pinExecInId":null,"execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"10"}},"rows":2,"colums":10}},{"position":{"x":968,"y":98},"nodeDescription":{"nodeTitle":"Print","execIn":true,"pinExecInId":"16","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"17"}},"inputs":{"input0":{"inputTitle":"Value","dataType":"Data","defValue":"hello","pinInId":"18"}},"rows":3,"colums":12}},{"position":{"x":706,"y":89},"nodeDescription":{"nodeTitle":"Branch","execIn":true,"pinExecInId":"28","execOut":{"execOut0":{"execOutTitle":" True","pinExecOutId":"29"},"execOut1":{"execOutTitle":" False","pinExecOutId":"31"}},"inputs":{"input0":{"inputTitle":"Bool","dataType":"Boolean","defValue":true,"pinInId":"33"}},"rows":3,"colums":12}}],"wireData":[{"srcId":"31","destId":"16"},{"srcId":"29","destId":"16"},{"srcId":"10","destId":"28"}]}`; -document.getElementById("import").addEventListener("click", () => { - // document.getElementById("save-menu").classList.toggle("hidden", true); - // document.getElementById("saving").classList.toggle("hidden", true); - let importMenu = document.getElementById("import-menu"); - [...document.getElementsByClassName("sidebox")].forEach(value => { - if (value !== importMenu) { - value.classList.toggle("hidden", true); - } - else { - value.classList.toggle("hidden", false); - } - }) - document.getElementById("import-btn").addEventListener("click", (e) => { - - let file = document.getElementById("upload-json").files; - if (file.length == 0) { - showAlert("Upload File First"); - } - else { - try { - document.getElementById("import-menu").classList.toggle("hidden", true); - let fr = new FileReader(); - fr.onload = function (e) { - // let result = JSON.parse(e.target.result); - // console.log(result); - let script = e.target.result - new Import(stage, layer, stage.findOne('#wireLayer'), script); - document.getElementById("import-menu").classList.toggle("hidden", true); - } - fr.readAsText(file.item(0)); - } - catch (err) { - document.getElementById("console-window").classList.toggle("hidden", false); - let codeDoc = document.getElementById("console").contentWindow.document; - codeDoc.open(); - codeDoc.writeln( - `\n - - - - "Error Occurred In Importing The JSON"
    - ${err} -
    - - - ` - ); - codeDoc.close(); - } - } - }); -}) -document.getElementById("live-code-refresh").addEventListener("click", () => { - let script = new VSToJS(stage, layer, "live-code-refresh").script; - refresh(script); -} -); -document.onkeydown = (e) => { - // e.preventDefault(); - if (e.code == 'KeyQ' && e.ctrlKey) { - let script = new VSToJS(stage, layer, "live-code-refresh").script; - refresh(script); - } -} -document.getElementById("live-code-arrow").addEventListener("click", () => { - document.getElementById("live-code-container").classList.toggle("live-code-closed"); - document.getElementById("live-code-arrow").classList.toggle("live-code-arrow-clicked"); -}); -document.getElementById("Code").addEventListener("click", () => { - document.getElementById("live-code-container").classList.toggle("live-code-closed"); - document.getElementById("live-code-refresh").click(); - document.getElementById("live-code-arrow").classList.toggle("live-code-arrow-clicked"); -}) -document.getElementById("Console").addEventListener("click", (e) => { - document.getElementById("console-window").classList.toggle("hidden"); -}) -document.getElementById("cross-console").addEventListener("click", (e) => { - document.getElementById("console-window").classList.toggle("hidden", true); -}); -document.getElementById("cross-upload-cross").addEventListener("click", (e) => { - document.getElementById("import-menu").classList.toggle("hidden", true); -}); -document.getElementById("reload").addEventListener("click", (e) => { - prompLastSave(stage, layer, stage.findOne('#wireLayer')); -}); - -document.getElementById("refresh").addEventListener("click", (e) => { - prompRefreshOrStarter("refresh", stage); -}); -document.getElementById("starter").addEventListener("click", (e) => { - prompRefreshOrStarter("starter", stage); -}) - - - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5713b12 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1104 @@ +{ + "name": "codewire", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codewire", + "version": "1.0.0", + "devDependencies": { + "vite": "^7.3.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..04e6611 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "codewire", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "node test/compiler.test.js", + "test:compiler": "node test/compiler.test.js", + "setup:hooks": "node scripts/setup-git-hooks.js" + }, + "devDependencies": { + "vite": "^7.3.1" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..a80808a --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,626 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + vite: + specifier: ^7.3.1 + version: 7.3.1 + +packages: + + /@esbuild/aix-ppc64@0.27.3: + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.27.3: + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.27.3: + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.27.3: + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.27.3: + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.27.3: + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.27.3: + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.27.3: + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.27.3: + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.27.3: + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.27.3: + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.27.3: + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.27.3: + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.27.3: + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.27.3: + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.27.3: + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.27.3: + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-arm64@0.27.3: + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.27.3: + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-arm64@0.27.3: + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.27.3: + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openharmony-arm64@0.27.3: + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.27.3: + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.27.3: + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.27.3: + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.27.3: + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm-eabi@4.59.0: + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.59.0: + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.59.0: + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.59.0: + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-freebsd-arm64@4.59.0: + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-freebsd-x64@4.59.0: + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.59.0: + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.59.0: + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.59.0: + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.59.0: + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-loong64-gnu@4.59.0: + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-loong64-musl@4.59.0: + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-ppc64-gnu@4.59.0: + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-ppc64-musl@4.59.0: + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.59.0: + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-musl@4.59.0: + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.59.0: + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.59.0: + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.59.0: + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-openbsd-x64@4.59.0: + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-openharmony-arm64@4.59.0: + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.59.0: + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.59.0: + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-gnu@4.59.0: + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.59.0: + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@types/estree@1.0.8: + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + dev: true + + /esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + dev: true + + /fdir@6.5.0(picomatch@4.0.3): + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.3 + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true + + /picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + dev: true + + /postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + dev: true + + /rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 + fsevents: 2.3.3 + dev: true + + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + dev: true + + /tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + dev: true + + /vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.8 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + dev: true diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg new file mode 100644 index 0000000..c6bff55 --- /dev/null +++ b/scripts/git-hooks/commit-msg @@ -0,0 +1,30 @@ +#!/bin/sh +# Enforces Conventional Commits: type(scope): description +# Types: feat, fix, chore, docs, refactor, style, test, perf + +commit_msg_file="$1" +if [ ! -f "$commit_msg_file" ]; then + echo "Error: No commit message file." >&2 + exit 1 +fi + +# Read first line, trim +first_line=$(head -n 1 "$commit_msg_file" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + +# Pattern: type or type(scope) followed by : and space and message +# Also allow Merge commits and Revert +if echo "$first_line" | grep -qE '^(Merge |Revert "|feat|fix|chore|docs|refactor|style|test|perf)(\([a-zA-Z0-9_-]+\))?!?: .+'; then + exit 0 +fi + +# Allow merge commit (no colon) +if echo "$first_line" | grep -qE '^Merge (branch|pull request|remote-tracking)'; then + exit 0 +fi + +echo "Invalid commit message format." >&2 +echo "Use: type(scope): description" >&2 +echo "Types: feat, fix, chore, docs, refactor, style, test, perf" >&2 +echo "Example: feat(nodes): add custom node type" >&2 +echo "Your first line was: $first_line" >&2 +exit 1 diff --git a/scripts/git-hooks/pre-push b/scripts/git-hooks/pre-push new file mode 100644 index 0000000..64b441c --- /dev/null +++ b/scripts/git-hooks/pre-push @@ -0,0 +1,24 @@ +#!/bin/sh +# Enforces branch name format: type/short-description +# Allowed types: feature, fix, chore, docs, refactor, test +# master and main are always allowed. + +# Get current branch +current_branch=$(git rev-parse --abbrev-ref HEAD) + +# Allow master and main +case "$current_branch" in + master|main) exit 0 ;; +esac + +# Must match type/description (lowercase, hyphens allowed) +if echo "$current_branch" | grep -qE '^(feature|fix|chore|docs|refactor|test)/[a-z0-9][a-z0-9_-]*$'; then + exit 0 +fi + +echo "Branch name does not follow conventions." >&2 +echo "Use: type/short-description" >&2 +echo "Types: feature, fix, chore, docs, refactor, test" >&2 +echo "Example: feature/node-registry" >&2 +echo "Current branch: $current_branch" >&2 +exit 1 diff --git a/scripts/setup-git-hooks.js b/scripts/setup-git-hooks.js new file mode 100644 index 0000000..2be936d --- /dev/null +++ b/scripts/setup-git-hooks.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +/** + * Copies Git hook scripts from scripts/git-hooks/ into .git/hooks/ + * so branch and commit message conventions are enforced. + */ + +import { copyFileSync, existsSync, mkdirSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(__dirname, '..'); +const hooksSrc = join(repoRoot, 'scripts', 'git-hooks'); +const hooksDest = join(repoRoot, '.git', 'hooks'); + +const hookNames = ['commit-msg', 'pre-push']; + +if (!existsSync(join(repoRoot, '.git', 'HEAD'))) { + console.error('Not a Git repo (no .git/HEAD). Run this from the repo root.'); + process.exit(1); +} + +if (!existsSync(hooksSrc)) { + console.error('scripts/git-hooks not found.'); + process.exit(1); +} + +if (!existsSync(hooksDest)) { + mkdirSync(hooksDest, { recursive: true }); +} + +for (const name of hookNames) { + const src = join(hooksSrc, name); + const dest = join(hooksDest, name); + if (!existsSync(src)) { + console.warn('Skip (missing):', name); + continue; + } + copyFileSync(src, dest); + console.log('Installed:', name); +} + +console.log('Git hooks are installed. See CONVENTIONS.md for branch and commit formats.'); diff --git a/images/Capture.JPG b/src/images/Capture.JPG similarity index 100% rename from images/Capture.JPG rename to src/images/Capture.JPG diff --git a/images/Code Wire Logo.ai b/src/images/Code Wire Logo.ai similarity index 100% rename from images/Code Wire Logo.ai rename to src/images/Code Wire Logo.ai diff --git a/images/Code Wire Logo.png b/src/images/Code Wire Logo.png similarity index 100% rename from images/Code Wire Logo.png rename to src/images/Code Wire Logo.png diff --git a/images/Code Wire Logo.svg b/src/images/Code Wire Logo.svg similarity index 100% rename from images/Code Wire Logo.svg rename to src/images/Code Wire Logo.svg diff --git a/images/Factorial.png b/src/images/Factorial.png similarity index 100% rename from images/Factorial.png rename to src/images/Factorial.png diff --git a/images/FibonacciSeries.png b/src/images/FibonacciSeries.png similarity index 100% rename from images/FibonacciSeries.png rename to src/images/FibonacciSeries.png diff --git a/images/PrimeNumberCheck.png b/src/images/PrimeNumberCheck.png similarity index 100% rename from images/PrimeNumberCheck.png rename to src/images/PrimeNumberCheck.png diff --git a/images/Untitled Diagram.drawio.png b/src/images/Untitled Diagram.drawio.png similarity index 100% rename from images/Untitled Diagram.drawio.png rename to src/images/Untitled Diagram.drawio.png diff --git a/images/add_ex.JPG b/src/images/add_ex.JPG similarity index 100% rename from images/add_ex.JPG rename to src/images/add_ex.JPG diff --git a/images/fib.png b/src/images/fib.png similarity index 100% rename from images/fib.png rename to src/images/fib.png diff --git a/images/httpreq.png b/src/images/httpreq.png similarity index 100% rename from images/httpreq.png rename to src/images/httpreq.png diff --git a/images/print_ctx_menu.JPG b/src/images/print_ctx_menu.JPG similarity index 100% rename from images/print_ctx_menu.JPG rename to src/images/print_ctx_menu.JPG diff --git a/images/print_example.JPG b/src/images/print_example.JPG similarity index 100% rename from images/print_example.JPG rename to src/images/print_example.JPG diff --git a/images/print_node.JPG b/src/images/print_node.JPG similarity index 100% rename from images/print_node.JPG rename to src/images/print_node.JPG diff --git a/images/right-chevron.svg b/src/images/right-chevron.svg similarity index 100% rename from images/right-chevron.svg rename to src/images/right-chevron.svg diff --git a/images/sqq1.png b/src/images/sqq1.png similarity index 100% rename from images/sqq1.png rename to src/images/sqq1.png diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..08dc3d6 --- /dev/null +++ b/src/index.html @@ -0,0 +1,267 @@ + + + + + + + + CodeWire + + + + + + + + + + +
    +
    + + +
    +
    + +
    +
    +
    + + + + +
    Add Variable
    +
    +
    +
    + + + + + + +
    + +
    +
    +
    +
    Variable List
    +
    +
    +
    +
      +
    +
    +
    +
    Functions
    +
    +
    +
    +
      +
      +
      +
      +
      +
      +
      + +
      +
      + +
      +
      +
      +
      +
      +

      Live Code

      +
      +
      + +
      +
      + +
      +
      + + + + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/js/app.js b/src/js/app.js new file mode 100644 index 0000000..44a5a24 --- /dev/null +++ b/src/js/app.js @@ -0,0 +1,202 @@ +import './registry/index.js'; +import { AppStage } from './core/stage.js' +// import { SelectionBox } from './editor/selectionBox.js' +import { DragAndDrop } from './editor/dragAndDrop.js' +import { Wiring } from './nodes/wiring.js' +import { ContextMenu } from './editor/contextMenu.js' +import { leftPanel } from './ui/variablePanel.js' +import { variableList } from './ui/variableList.js' +import { VSToJS } from './compiler/compiler.js' +import { Delete } from './editor/deleteHandler.js' +import { Export, Import, Save, prompLastSave } from './persistence/saveAndLoad.js' +import { showAlert, prompRefreshOrStarter } from './ui/dialogs.js' +import { refresh } from './compiler/codePreview.js' +import { enableNodeGroups } from './editor/nodeGroup.js' +import { tabManager } from './editor/tabManager.js' +import { initFunctionPanel } from './ui/functionPanel.js' + +let stage = AppStage.getStage(document.getElementById("container").clientWidth, document.getElementById("container").clientHeight, 'container'); +var layer = new Konva.Layer({ + id: 'main_layer' +}); +let wireLayer = new Konva.Layer({ + id: 'wireLayer', +}); +let dragLayer = new Konva.Layer({ + id: 'dragLayer', +}); +stage.add(wireLayer); +stage.add(layer); +stage.add(dragLayer); +layer.moveToBottom(); +stage.container().style.backgroundPosition = `${stage.position().x} ${stage.position().y}`; + +tabManager.init(stage, layer, wireLayer, dragLayer); + +// SelectionBox.setSelectionBox(layer, stage); +enableNodeGroups(stage); +Delete.enableDelete(stage, layer); +Wiring.enableWiring(stage); +ContextMenu.contextMenu(stage); +let panel = new leftPanel(); +variableList.init(layer, stage); +tabManager.getTab('main').variables = variableList.variables; +initFunctionPanel(); +layer.draw(); + +document.getElementById('add-tab-btn').addEventListener('click', () => { + const name = prompt('Enter function name:'); + if (name && name.trim()) { + tabManager.createTab(name.trim()); + } +}); + +tabManager.on('tabSwitched', ({ to }) => { + variableList.layer = to.layer; + variableList.switchToTab(to.variables); +}); +document.getElementById("Run").addEventListener("click", (e) => { + try { + let script = new VSToJS(stage, layer, "Run").script; + // let script = new VSToJS(stage, layer, "live-code-refresh").script; + refresh(script); + } + catch (err) { + + } +}); +// stage.on('mouseup', () => { +// console.log("x"); +// }) + +new Save(stage, layer, stage.findOne('#wireLayer')); +new Export(stage, layer, stage.findOne('#wireLayer')); +// let script = `{"variables":[{"name":"sadsad","dataType":"Number","value":"0"},{"name":"sadsaddd","dataType":"Array","value":"[1,2]"}],"nodesData":[{"position":{"x":511,"y":16},"nodeDescription":{"nodeTitle":"Begin","execIn":false,"pinExecInId":null,"execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"10"}},"rows":2,"colums":10}},{"position":{"x":968,"y":98},"nodeDescription":{"nodeTitle":"Print","execIn":true,"pinExecInId":"16","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"17"}},"inputs":{"input0":{"inputTitle":"Value","dataType":"Data","defValue":"hello","pinInId":"18"}},"rows":3,"colums":12}},{"position":{"x":706,"y":89},"nodeDescription":{"nodeTitle":"Branch","execIn":true,"pinExecInId":"28","execOut":{"execOut0":{"execOutTitle":" True","pinExecOutId":"29"},"execOut1":{"execOutTitle":" False","pinExecOutId":"31"}},"inputs":{"input0":{"inputTitle":"Bool","dataType":"Boolean","defValue":true,"pinInId":"33"}},"rows":3,"colums":12}}],"wireData":[{"srcId":"31","destId":"16"},{"srcId":"29","destId":"16"},{"srcId":"10","destId":"28"}]}`; +// ── Import modal logic ── +const importOverlay = document.getElementById("import-overlay"); +const importModal = document.getElementById("import-modal"); +const importDropzone = document.getElementById("import-dropzone"); +const importFileInput = document.getElementById("upload-json"); +const importFileInfo = document.getElementById("import-file-info"); +const importFilename = document.getElementById("import-filename"); +const importFileClear = document.getElementById("import-file-clear"); +const importCloseBtn = document.getElementById("import-close-btn"); +const importCancelBtn = document.getElementById("import-cancel-btn"); +const importBtn = document.getElementById("import-btn"); + +function openImportModal() { + [...document.getElementsByClassName("sidebox")].forEach(v => v.classList.add("hidden")); + importFileInput.value = ""; + importFileInfo.classList.add("hidden"); + importFilename.textContent = ""; + importDropzone.classList.remove("drag-over"); + importOverlay.classList.remove("hidden"); +} + +function closeImportModal() { + importOverlay.classList.add("hidden"); +} + +function showSelectedFile(file) { + if (!file) return; + importFilename.textContent = file.name; + importFileInfo.classList.remove("hidden"); +} + +document.getElementById("import").addEventListener("click", openImportModal); + +importOverlay.addEventListener("click", (e) => { + if (e.target === importOverlay) closeImportModal(); +}); +importCloseBtn.addEventListener("click", closeImportModal); +importCancelBtn.addEventListener("click", closeImportModal); + +importDropzone.addEventListener("click", () => importFileInput.click()); + +importDropzone.addEventListener("dragenter", (e) => { e.preventDefault(); importDropzone.classList.add("drag-over"); }); +importDropzone.addEventListener("dragover", (e) => { e.preventDefault(); importDropzone.classList.add("drag-over"); }); +importDropzone.addEventListener("dragleave", () => { importDropzone.classList.remove("drag-over"); }); +importDropzone.addEventListener("drop", (e) => { + e.preventDefault(); + importDropzone.classList.remove("drag-over"); + const files = e.dataTransfer.files; + if (files.length) { + importFileInput.files = files; + showSelectedFile(files[0]); + } +}); + +importFileInput.addEventListener("change", () => { + if (importFileInput.files.length) showSelectedFile(importFileInput.files[0]); +}); + +importFileClear.addEventListener("click", () => { + importFileInput.value = ""; + importFileInfo.classList.add("hidden"); + importFilename.textContent = ""; +}); + +importBtn.addEventListener("click", () => { + const files = importFileInput.files; + if (!files.length) { + showAlert("Upload File First"); + return; + } + try { + const fr = new FileReader(); + fr.onload = function (e) { + new Import(stage, layer, stage.findOne('#wireLayer'), e.target.result); + closeImportModal(); + }; + fr.readAsText(files[0]); + } catch (err) { + document.getElementById("console-window").classList.toggle("hidden", false); + let codeDoc = document.getElementById("console").contentWindow.document; + codeDoc.open(); + codeDoc.writeln( + `\n + "Error Occurred In Importing The JSON"
      ${err}
      ` + ); + codeDoc.close(); + } +}); +document.getElementById("live-code-refresh").addEventListener("click", () => { + let script = new VSToJS(stage, layer, "live-code-refresh").script; + refresh(script); +} +); +document.onkeydown = (e) => { + // e.preventDefault(); + if (e.code == 'KeyQ' && e.ctrlKey) { + let script = new VSToJS(stage, layer, "live-code-refresh").script; + refresh(script); + } +} +document.getElementById("live-code-arrow").addEventListener("click", () => { + document.getElementById("live-code-container").classList.toggle("live-code-closed"); + document.getElementById("live-code-arrow").classList.toggle("live-code-arrow-clicked"); +}); +document.getElementById("Code").addEventListener("click", () => { + document.getElementById("live-code-container").classList.toggle("live-code-closed"); + document.getElementById("live-code-refresh").click(); + document.getElementById("live-code-arrow").classList.toggle("live-code-arrow-clicked"); +}) +document.getElementById("Console").addEventListener("click", (e) => { + document.getElementById("console-window").classList.toggle("hidden"); +}) +document.getElementById("cross-console").addEventListener("click", (e) => { + document.getElementById("console-window").classList.toggle("hidden", true); +}); +document.getElementById("reload").addEventListener("click", (e) => { + prompLastSave(stage, layer, stage.findOne('#wireLayer')); +}); + +document.getElementById("refresh").addEventListener("click", (e) => { + prompRefreshOrStarter("refresh", stage); +}); +document.getElementById("starter").addEventListener("click", (e) => { + prompRefreshOrStarter("starter", stage); +}) + + + diff --git a/javascript/VisualScriptToJavascript/builtInFunctions.js b/src/js/compiler/builtInFunctions.js similarity index 93% rename from javascript/VisualScriptToJavascript/builtInFunctions.js rename to src/js/compiler/builtInFunctions.js index c405900..f5bbe3b 100644 --- a/javascript/VisualScriptToJavascript/builtInFunctions.js +++ b/src/js/compiler/builtInFunctions.js @@ -61,6 +61,11 @@ export const BuilInFunctions = { return [(_value !== null), _value]; } `, + _sleep: `function _sleep(_ms){ + const _start = Date.now(); + while (Date.now() - _start < _ms) {} + } + `, } \ No newline at end of file diff --git a/javascript/VisualScriptToJavascript/liveCode.js b/src/js/compiler/codePreview.js similarity index 100% rename from javascript/VisualScriptToJavascript/liveCode.js rename to src/js/compiler/codePreview.js diff --git a/src/js/compiler/compiler.js b/src/js/compiler/compiler.js new file mode 100644 index 0000000..09b0956 --- /dev/null +++ b/src/js/compiler/compiler.js @@ -0,0 +1,305 @@ +import { variableList } from '../ui/variableList.js' +import { showAlert } from '../ui/dialogs.js' +import { BuilInFunctions } from './builtInFunctions.js' +import { runExecCodegen, runExprCodegen, hasType } from '../registry/index.js' +import { tabManager } from '../editor/tabManager.js' +export var VSToJS = class { + + constructor(stage, layer, isRunOrCode) { + this.script = ''; + this.builtin_functions = {}; + this.nodeCount = 0; + this.isRunOrCode = isRunOrCode; + this.compileIssues = []; + this._callCounter = 0; + + this.compileFunctionTabs(); + + const mainTab = tabManager.getTab('main'); + const globalVars = mainTab ? mainTab.variables : variableList.variables; + for (let variable of globalVars) { + this.script += `let ${variable.name} = ${variable.value};\n`; + } + let begin = this.getBegin(stage); + if (begin) { + try { + this.coreAlgorithm(begin); + if (this.compileIssues.length > 0) { + showAlert("Compile failed — fix these issues first:
      " + this.compileIssues.join("
      ")); + this.script = ''; + return; + } + if (this.isRunOrCode == "Run") { + document.getElementById("console-window").classList.toggle("hidden", false); + let codeDoc = document.getElementById("console").contentWindow.document; + // console.log("run"); + codeDoc.open(); + codeDoc.writeln( + `\n + + +

      + + + + ` + ); + codeDoc.close(); + } + } + catch (err) { + document.getElementById("console-window").classList.toggle("hidden", false); + let codeDoc = document.getElementById("console").contentWindow.document; + this.script = ''; + this.builtin_functions = {}; + codeDoc.open(); + codeDoc.writeln( + `\n + + + + Recheck the nodes
      + ${err.name === 'RangeError' ? 'CyclicDependence : Irresolvable Cycle(s) Exists' : `UnknownException: Improve The Editor By Opening Issue On GitHub(Just Attach The Exported Graph)`} +
      + + + ` + ); + } + } + } + compileFunctionTabs() { + const funcTabs = tabManager.getAllFunctionTabs(); + for (const tab of funcTabs) { + const funcBegin = tab.layer.findOne('#FunctionBegin'); + if (!funcBegin) continue; + + const params = tab.inputParams.map(p => p.name).join(', '); + const savedScript = this.script; + this.script = ''; + + if (tab.variables && tab.variables.length > 0) { + for (const v of tab.variables) { + this.script += `let ${v.name} = ${v.value};\n`; + } + } + + const execOutPins = this.getExecOut(funcBegin); + for (let each of execOutPins) { + this.coreAlgorithm(each); + } + + const bodyScript = this.script; + this.script = savedScript; + this.script += `function ${tab.name}(${params}) {\n${bodyScript}}\n`; + } + } + + getBegin(stage) { + let X = stage.find("#Begin"); + if (X.length == 0) { + showAlert("Include Begin Node"); + } + else if (X.length > 1) { + showAlert("Multiple Begin Nodes"); + } + else return X[0]; + } + getExecOut(node) { + let X = []; + for (let aNode of node.customClass.execOutPins) { + if (aNode.wire) + X.push(aNode.wire.attrs.dest.getParent()); + else + X.push(null); + } + // console.log(X); + return X; + } + getSrcOutputPinNumber(grp, aNodeWire) { + let c = 0; + for (let eachPin of grp.customClass.outputPins) { + for (let aWire of eachPin.wire) { + if (aWire === aNodeWire) { + return c; + } + } + c++; + } + } + getInputPins(node) { + let X = []; + for (let aNode of node.customClass.inputPins) { + if (aNode.wire) { + X.push({ node: aNode.wire.attrs.src.getParent(), isWire: true, wire: aNode.wire, srcOutputPinNumber: this.getSrcOutputPinNumber(aNode.wire.attrs.src.getParent(), aNode.wire) }); + } + else { + const val = aNode.textBox ? aNode.textBox.textBox.text() : 'undefined'; + X.push({ node: val, isWire: false, wire: null, srcOutputPinNumber: null }); + } + } + return X; + } + coreAlgorithm(node) { + if (node == null) return; + + if (node.customClass.isOrphaned) { + this.compileIssues.push(`Skipped orphaned node: "${node.customClass.type.typeOfNode}"`); + let execOutPins = this.getExecOut(node); + for (let each of execOutPins) { + this.coreAlgorithm(each); + } + return; + } + + const nodeType = node.customClass.type.typeOfNode; + + if (nodeType === 'FunctionBegin') { + let execOutPins = this.getExecOut(node); + for (let each of execOutPins) { + this.coreAlgorithm(each); + } + return; + } + + if (nodeType === 'Return') { + let inputPins = this.getInputPins(node); + if (inputPins.length > 0) { + const nd = node.customClass.nodeDescription; + const outputNames = []; + if (nd.inputs) { + for (const key of Object.keys(nd.inputs)) { + outputNames.push(nd.inputs[key].inputTitle); + } + } + if (inputPins.length === 1) { + this.script += `return ${this.handleInputs(inputPins[0])};\n`; + } else { + const pairs = outputNames.map((name, i) => + `${name}: ${this.handleInputs(inputPins[i])}` + ).join(', '); + this.script += `return { ${pairs} };\n`; + } + } else { + this.script += `return;\n`; + } + return; + } + + if (nodeType.startsWith('Call ')) { + let inputPins = this.getInputPins(node); + let execOutPins = this.getExecOut(node); + const funcName = node.customClass.nodeDescription.calledFunctionName || nodeType.slice(5); + const args = inputPins.map(ip => this.handleInputs(ip)).join(', '); + const varName = `_call_${funcName}_${this._callCounter++}`; + this.script += `let ${varName} = ${funcName}(${args});\n`; + node._callResultVar = varName; + for (let each of execOutPins) { + this.coreAlgorithm(each); + } + return; + } + + let execOutPins = this.getExecOut(node); + let inputPins = this.getInputPins(node); + if (node.customClass.type.isGetSet) { + if (node.customClass.type.typeOfNode.slice(0, 3) == 'Set') { + this.script += `${node.customClass.type.typeOfNode.slice(4)} = ${this.handleInputs(inputPins[0])};\n`; + for (let each of execOutPins) { + this.coreAlgorithm(each); + } + } + } else { + const type = node.customClass.type.typeOfNode; + if (hasType(type)) { + runExecCodegen(type, this, node); + } + } + } + handleInputs(inputNode) { + + if (!inputNode.isWire) { + return inputNode.node; + } + + if (inputNode.wire && inputNode.wire.isMismatched) { + this.compileIssues.push(`Skipped type-mismatched wire connected to "${inputNode.node.customClass.type.typeOfNode}"`); + return "undefined"; + } + + if (inputNode.node.customClass.isOrphaned) { + this.compileIssues.push(`Skipped orphaned node: "${inputNode.node.customClass.type.typeOfNode}"`); + return "undefined"; + } + + const nodeType = inputNode.node.customClass.type.typeOfNode; + + if (nodeType === 'FunctionBegin') { + const nd = inputNode.node.customClass.nodeDescription; + if (nd.outputs) { + const outputKey = `output${inputNode.srcOutputPinNumber}`; + const paramName = nd.outputs[outputKey]?.outputTitle; + if (paramName) return paramName; + } + return 'undefined'; + } + + if (nodeType.startsWith('Call ')) { + const resultVar = inputNode.node._callResultVar; + if (resultVar) { + const nd = inputNode.node.customClass.nodeDescription; + if (nd.outputs) { + const outputKeys = Object.keys(nd.outputs); + if (outputKeys.length === 1) { + return resultVar; + } + const outputName = nd.outputs[`output${inputNode.srcOutputPinNumber}`]?.outputTitle; + if (outputName) return `${resultVar}.${outputName}`; + } + return resultVar; + } + return 'undefined'; + } + + let inputPins = this.getInputPins(inputNode.node); + if (inputNode.node.customClass.type.isGetSet) { + return `${inputNode.node.customClass.type.typeOfNode.slice(4)}`; + } + const type = inputNode.node.customClass.type.typeOfNode; + if (hasType(type)) { + const result = runExprCodegen(type, this, inputNode); + if (result !== undefined) return result; + } + return ''; + } + + + +}; \ No newline at end of file diff --git a/src/js/core/colorMap.js b/src/js/core/colorMap.js new file mode 100644 index 0000000..51b7201 --- /dev/null +++ b/src/js/core/colorMap.js @@ -0,0 +1,48 @@ +/** + * Color scheme: node categories (context menu + canvas) use distinct hues for quick recognition. + * Data types (Number, String, etc.) and UI (MainLabel, Group*) are separate. + */ +export const colorMap = { + // Data types (wires, inputs) + 'Number': '#00d0fa', + 'String': '#ed1a95', + 'Boolean': '#f30909', + 'Array': '#ccff33', + 'Data': '#ff7900', + // UI + 'MainLabel': '#ffffff', + 'MainLabelBox': '#3282b8', + 'MainBoxGradient': { + 'start': '#28313b', + 'end': '#485461', + }, + 'Text': '#ffffff', + // Node categories (menu + node headers) + 'Begin': '#e65100', // Flow – warm orange (entry) + 'Print': '#c62828', // I/O – red (output, dialogs) + 'Logic': '#7b1fa2', // Control flow – purple + 'Math': '#1565c0', // Numeric – blue + 'Func': '#00838f', // Function/API – cyan + 'Get': '#2e7d32', // Array / access – green + 'Str': '#ad1457', // String – pink/magenta + 'Obj': '#00695c', // Object / Map – teal + 'FunctionBegin': '#ff6d00', // Function entry – warm orange + 'Return': '#d50000', // Function exit – red + 'Call': '#00bfa5', // Function call – teal + 'GroupBody': 'rgba(255, 255, 255, 0.31)', + 'GroupBorder': 'rgba(255,255,255,0.3)', + 'GroupTitleBar': 'rgba(41,68,150,0.8)', + 'GroupTitleText': '#e0e0e0', + 'GroupHandle': 'rgba(255,255,255,0.35)', + 'GroupHandleBorder': 'rgba(255,255,255,0.5)', + 'GroupPreview': 'rgba(100,150,255,0.08)', + 'GroupPreviewBorder': 'rgba(100,150,255,0.5)', +}; + +export function lightenHex(hex, amount = 0.45) { + const n = parseInt(hex.replace('#', ''), 16); + const r = Math.min(255, ((n >> 16) & 0xff) + Math.round((255 - ((n >> 16) & 0xff)) * amount)); + const g = Math.min(255, ((n >> 8) & 0xff) + Math.round((255 - ((n >> 8) & 0xff)) * amount)); + const b = Math.min(255, (n & 0xff) + Math.round((255 - (n & 0xff)) * amount)); + return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)}`; +} \ No newline at end of file diff --git a/javascript/AppStage/intiStage.js b/src/js/core/stage.js similarity index 95% rename from javascript/AppStage/intiStage.js rename to src/js/core/stage.js index 3e5ef61..3384507 100644 --- a/javascript/AppStage/intiStage.js +++ b/src/js/core/stage.js @@ -41,7 +41,7 @@ export var AppStage = { stage.container().focus(); stage.on('mousedown', function (e) { - if (e.target === stage && (e.evt.button == 0 || e.evt.button == 1)) { + if (e.target === stage && (e.evt.button == 0 || e.evt.button == 1) && !e.evt.shiftKey) { stage.draggable(true); } }); @@ -57,13 +57,11 @@ export var AppStage = { }) window.addEventListener('resize', () => { let container = document.querySelector('#container'); - // console.log("Resized"); let containerWidth = container.offsetWidth; - let scale = containerWidth / stage.width(); stage.width(container.offsetWidth); stage.height(container.offsetHeight); stage.draw(); }); return stage; } -} \ No newline at end of file +} diff --git a/src/js/editor/contextMenu.js b/src/js/editor/contextMenu.js new file mode 100644 index 0000000..f40004c --- /dev/null +++ b/src/js/editor/contextMenu.js @@ -0,0 +1,352 @@ +import { setLocationOfNode } from '../nodes/nodePosition.js' +import { Nodes } from '../nodes/nodeFactory.js' +import { variableList } from '../ui/variableList.js' +import { deleteProgramNode, deleteWire } from './deleteHandler.js' +import { getMenuOrderGroupedByCategory, getDefinition } from '../registry/index.js' +import { colorMap, lightenHex } from '../core/colorMap.js' +import { tabManager } from './tabManager.js' + +const CATEGORY_LABELS = { + Begin: 'Flow', + Print: 'I/O', + Logic: 'Logic', + Math: 'Math', + Str: 'String', + Obj: 'Object / Map', + Get: 'Array', + Func: 'Utility', +}; + +const DEFAULT_EXPANDED_CATEGORIES = new Set(['Begin', 'Logic']); + +export var ContextMenu = { + contextMenu: function (stage) { + let contextMenu = document.getElementById("ctx-menu-container"); + let contextMenuList = document.getElementById("context-menu"); + let deleteCtxMenu = document.getElementById("delete-ctx-container"); + let getSetCtxMenu = document.getElementById("get-set-ctx-menu-container"); + let searchBar = document.getElementById("ctx-search-bar"); + + // Build context menu from registry, grouped by category (collapsible sections) + contextMenuList.innerHTML = ''; + const { categoryOrder, groups } = getMenuOrderGroupedByCategory(); + for (const category of categoryOrder) { + const ids = groups[category] || []; + if (ids.length === 0) continue; + const section = document.createElement('div'); + section.className = 'ctx-menu-section'; + section.dataset.category = category; + const expanded = DEFAULT_EXPANDED_CATEGORIES.has(category); + if (!expanded) section.classList.add('ctx-menu-section--collapsed'); + const headerColor = colorMap[category] || colorMap['Text']; + const menuTextColor = lightenHex(headerColor); + const header = document.createElement('div'); + header.className = 'ctx-menu-section-header'; + header.style.borderLeftColor = headerColor; + header.style.color = menuTextColor; + const arrow = document.createElement('span'); + arrow.className = 'ctx-menu-section-arrow'; + arrow.textContent = expanded ? '\u25BC' : '\u25B6'; + const label = document.createElement('span'); + label.textContent = CATEGORY_LABELS[category] ?? category; + header.appendChild(arrow); + header.appendChild(label); + header.title = 'Click to expand/collapse'; + const body = document.createElement('div'); + body.className = 'ctx-menu-section-body'; + for (const id of ids) { + const def = getDefinition(id); + if (!def) continue; + const item = document.createElement('div'); + item.className = 'context-menu-items'; + item.textContent = def.label; + item.dataset.nodeId = id; + item.style.borderLeftColor = headerColor; + item.style.color = menuTextColor; + body.appendChild(item); + } + header.addEventListener('click', (e) => { + e.stopPropagation(); + const collapsed = section.classList.toggle('ctx-menu-section--collapsed'); + arrow.textContent = collapsed ? '\u25B6' : '\u25BC'; + }); + section.appendChild(header); + section.appendChild(body); + contextMenuList.appendChild(section); + } + // Variables section (expand/collapse) for Get/Set items added by variableList + const variablesColor = colorMap['Get'] || colorMap['Text']; + const variablesMenuColor = lightenHex(variablesColor); + const variablesSection = document.createElement('div'); + variablesSection.className = 'ctx-menu-section'; + variablesSection.dataset.category = 'Variables'; + variablesSection.classList.add('ctx-menu-section--collapsed'); + const variablesHeader = document.createElement('div'); + variablesHeader.className = 'ctx-menu-section-header'; + variablesHeader.style.borderLeftColor = variablesColor; + variablesHeader.style.color = variablesMenuColor; + const variablesArrow = document.createElement('span'); + variablesArrow.className = 'ctx-menu-section-arrow'; + variablesArrow.textContent = '\u25B6'; + const variablesLabel = document.createElement('span'); + variablesLabel.textContent = 'Variables'; + variablesHeader.appendChild(variablesArrow); + variablesHeader.appendChild(variablesLabel); + variablesHeader.title = 'Click to expand/collapse'; + const variablesBody = document.createElement('div'); + variablesBody.className = 'ctx-menu-section-body'; + variablesBody.id = 'context-menu-variables-body'; + variablesHeader.addEventListener('click', (e) => { + e.stopPropagation(); + const collapsed = variablesSection.classList.toggle('ctx-menu-section--collapsed'); + variablesArrow.textContent = collapsed ? '\u25B6' : '\u25BC'; + }); + variablesSection.appendChild(variablesHeader); + variablesSection.appendChild(variablesBody); + contextMenuList.appendChild(variablesSection); + let draggedVariableInfo = { + name: null, + dataType: null, + }; + function toggleContextMenu(location, show) { + if (show) { + contextMenu.classList.toggle("hidden", false); + contextMenu.style.left = location[0] + 'px'; + contextMenu.style.top = location[1] + 'px'; + searchBar.focus(); + } + else { + contextMenu.classList.toggle("hidden", true); + searchBar.value = ''; + resetSectionsVisibility(); + } + } + function resetSectionsVisibility() { + const sections = contextMenuList.querySelectorAll('.ctx-menu-section'); + sections.forEach((section) => { + const category = section.dataset.category; + const expanded = DEFAULT_EXPANDED_CATEGORIES.has(category); + section.classList.toggle('ctx-menu-section--collapsed', !expanded); + section.classList.remove('hidden'); + const arr = section.querySelector('.ctx-menu-section-arrow'); + if (arr) arr.textContent = expanded ? '\u25BC' : '\u25B6'; + const items = section.querySelectorAll('.context-menu-items'); + items.forEach((el) => el.classList.remove('hidden')); + }); + contextMenuList.querySelectorAll('.context-menu-items').forEach((el) => el.classList.remove('hidden')); + } + ContextMenu.resetFilter = function () { + const bar = document.getElementById('ctx-search-bar'); + const list = document.getElementById('context-menu'); + if (bar) bar.value = ''; + if (list) { + list.querySelectorAll('.ctx-menu-section').forEach((section) => { + const category = section.dataset.category; + const expanded = DEFAULT_EXPANDED_CATEGORIES.has(category); + section.classList.toggle('ctx-menu-section--collapsed', !expanded); + section.classList.remove('hidden'); + const arr = section.querySelector('.ctx-menu-section-arrow'); + if (arr) arr.textContent = expanded ? '\u25BC' : '\u25B6'; + section.querySelectorAll('.context-menu-items').forEach((el) => el.classList.remove('hidden')); + }); + list.querySelectorAll('.context-menu-items').forEach((el) => el.classList.remove('hidden')); + } + }; + function toggleDeleteCtxMenu(location, show) { + if (show) { + deleteCtxMenu.classList.toggle("hidden", false); + deleteCtxMenu.style.left = location[0] + 'px'; + deleteCtxMenu.style.top = location[1] + 'px'; + + } + else { + deleteCtxMenu.classList.toggle("hidden", true); + } + } + function toggleGetSetCtxMenu(location, show) { + if (show) { + getSetCtxMenu.classList.toggle("hidden", false); + getSetCtxMenu.style.left = location[0] + 'px'; + getSetCtxMenu.style.top = location[1] + 'px'; + } + else { + getSetCtxMenu.classList.toggle("hidden", true); + } + } + ContextMenu.addEventToCtxMenuItems = function (e) { + e.addEventListener('click', function (ev) { + if (ev.target.classList.contains('ctx-menu-section-header')) return; + makeNode(e, stage, tabManager.getActiveLayer(), toggleContextMenu); + }); + }; + searchBar.addEventListener("input", (e) => { + const key = e.target.value.toLowerCase().trim(); + const sections = contextMenuList.querySelectorAll('.ctx-menu-section'); + sections.forEach((section) => { + const body = section.querySelector('.ctx-menu-section-body'); + const items = body ? body.querySelectorAll('.context-menu-items') : []; + let hasMatch = false; + items.forEach((item) => { + const label = (item.textContent || '').toLowerCase(); + const nodeId = (item.dataset.nodeId || '').toLowerCase(); + const match = !key || label.includes(key) || nodeId.includes(key); + item.classList.toggle('hidden', !match); + if (match) hasMatch = true; + }); + section.classList.toggle('hidden', !key ? false : !hasMatch); + if (key && hasMatch) { + section.classList.remove('ctx-menu-section--collapsed'); + const arr = section.querySelector('.ctx-menu-section-arrow'); + if (arr) arr.textContent = '\u25BC'; + } + }); + const orphanItems = Array.from(contextMenuList.children).filter((el) => el.classList && el.classList.contains('context-menu-items')); + orphanItems.forEach((item) => { + const label = (item.textContent || '').toLowerCase(); + const match = !key || label.includes(key); + item.classList.toggle('hidden', !match); + }); + }); + + contextMenuList.querySelectorAll('.context-menu-items').forEach((el) => { + this.addEventToCtxMenuItems(el); + }); + + // let alreadyPresent = []; // to prevent adding multiple eventListeners + stage.on('contextmenu', function (e) { + e.evt.preventDefault(); + if (e.target === stage) { + let availY = stage.getContainer().getBoundingClientRect().height - e.evt.clientY; + let offY = 0, offX = 0; + if (availY <= 260) { + offY = -260; + } + let availX = stage.getContainer().getBoundingClientRect().width - e.evt.clientX; + if (availX <= 200) { + offX = -200; + } + toggleContextMenu([e.evt.clientX + offX, e.evt.clientY + offY], true); + } + else { + let parentGroup = e.target.getParent(); + let isNodeGroup = false; + let nodeGroupRef = null; + let tmp = e.target; + while (tmp && tmp !== stage) { + if (tmp.name && tmp.name() === 'aNodeGroup') { + isNodeGroup = true; + nodeGroupRef = tmp; + break; + } + tmp = tmp.getParent ? tmp.getParent() : null; + } + + toggleDeleteCtxMenu([e.evt.clientX - 130, e.evt.clientY - 35], true); + deleteCtxMenu.onclick = function () { + if (isNodeGroup && nodeGroupRef) { + nodeGroupRef.destroy(); + stage.draw(); + } + else if (parentGroup && parentGroup.name() == 'aProgramNodeGroup') { + deleteProgramNode(e, tabManager.getActiveLayer(), stage); + stage.draw(); + } + else if (e.target.name() == "isConnection") { + deleteWire(e.target); + stage.draw(); + } + toggleDeleteCtxMenu([e.evt.clientX - 130, e.evt.clientY - 35], false); + } + }; + + }); + stage.on('click', function (e) { + toggleContextMenu([e.evt.clientX, e.evt.clientY], false); + toggleDeleteCtxMenu([], false); + toggleGetSetCtxMenu([], false); + }); + document.addEventListener("click", (e) => { + if (e.target !== stage.getContainer() && e.target !== searchBar) + toggleContextMenu([0, 0], false); + if (e.target !== stage.getContainer()) { + toggleDeleteCtxMenu([], false); + toggleGetSetCtxMenu([], false); + } + }); + + getSetCtxMenu.addEventListener("click", (e) => { + let nodeType = e.target.innerHTML + " " + draggedVariableInfo.name; + let xx = e.target.parentElement.getBoundingClientRect().x - stage.getContainer().getBoundingClientRect().x; + let yy = e.target.parentElement.getBoundingClientRect().y - stage.getContainer().getBoundingClientRect().y; + let activeLayer = tabManager.getActiveLayer(); + if (e.target.innerHTML == "Get") { + Nodes.CreateNode(nodeType, { x: xx, y: yy }, activeLayer, stage, "Get", draggedVariableInfo.dataType, null); + } + else { + Nodes.CreateNode(nodeType, { x: xx, y: yy }, activeLayer, stage, "Set", draggedVariableInfo.dataType, null); + } + }); + + + stage.getContainer().addEventListener('dragenter', (e) => { + e.preventDefault(); + e.stopPropagation(); + }); + stage.getContainer().addEventListener('dragover', (e) => { + e.preventDefault(); + e.stopPropagation(); + }); + stage.getContainer().addEventListener('drop', (e) => { + e.preventDefault(); + if (e.dataTransfer.getData("functionTabId")) { + const funcTabId = e.dataTransfer.getData("functionTabId"); + const funcTab = tabManager.getTab(funcTabId); + if (funcTab && funcTab.saved) { + const saved = funcTab.saved; + const containerRect = stage.getContainer().getBoundingClientRect(); + const x = e.clientX - containerRect.x; + const y = e.clientY - containerRect.y; + const activeLayer = tabManager.getActiveLayer(); + Nodes.CreateCallNode(saved.name, saved.inputParams, saved.outputParams, + { x, y }, activeLayer, stage, saved.docString || ''); + activeLayer.draw(); + } + } else if (e.dataTransfer.getData("variableName")) { + toggleGetSetCtxMenu([e.clientX, e.clientY], true); + draggedVariableInfo = { + name: e.dataTransfer.getData("variableName"), + dataType: e.dataTransfer.getData("dataType"), + } + } + e.stopPropagation(); + }); + + tabManager.on('tabSwitched', () => { + toggleContextMenu([0, 0], false); + toggleDeleteCtxMenu([], false); + toggleGetSetCtxMenu([], false); + ContextMenu.resetFilter(); + }); + } +} + + + +function makeNode(e, stage, layer, toggleContextMenu) { + let xx = e.parentElement.getBoundingClientRect().x - stage.getContainer().getBoundingClientRect().x; + let yy = e.parentElement.getBoundingClientRect().y - stage.getContainer().getBoundingClientRect().y; + let dataType; + if (e.dataset.datatype) + dataType = e.dataset.datatype; + const type = e.dataset.nodeId || e.innerHTML; + let tmp = type.split(" "); + let isGetSet = ""; + if (tmp[0] == "Get") + isGetSet = "Get"; + else if (tmp[0] == "Set") + isGetSet = "Set"; + let defValue = null; + Nodes.CreateNode(type, { x: xx, y: yy }, layer, stage, isGetSet, dataType, defValue); + layer.draw(); + toggleContextMenu([], false); +} diff --git a/javascript/Delete/delete.js b/src/js/editor/deleteHandler.js similarity index 66% rename from javascript/Delete/delete.js rename to src/js/editor/deleteHandler.js index 30b2004..9481894 100644 --- a/javascript/Delete/delete.js +++ b/src/js/editor/deleteHandler.js @@ -1,54 +1,15 @@ export var Delete = { enableDelete: function (stage, layer) { - let ctrlIsPressed = false; - // console.log(wireLayer); - stage.on("click", (e) => { - // console.log(e.target.getParent()); - if (e.target.name() == "isConnection" && ctrlIsPressed) { - let aWire = e.target; - deleteWire(aWire); - let wireLayer = stage.findOne('#wireLayer'); - stage.draw(); - - } - else if (e.target !== stage && e.target.getParent().name() == "aProgramNodeGroup" && ctrlIsPressed) { - // console.log(e); - deleteProgramNode(e, layer, stage); - - } - }); - stage.container().addEventListener("keydown", (e) => { - e.preventDefault(); - if (e.code == "ControlLeft" && !ctrlIsPressed) { - ctrlIsPressed = true; - let wireLayer = stage.findOne('#wireLayer'); - let wireArray = wireLayer.find(".isConnection"); - wireArray.forEach(wire => { - wire.strokeWidth(5); - // wire.hitStrokeWidth(10); - }); - stage.draw(); - } - }); - stage.container().addEventListener("keyup", (e) => { - e.preventDefault(); - let wireLayer = stage.findOne('#wireLayer'); - if (e.code == "ControlLeft") { - let wireArray = wireLayer.find(".isConnection"); - wireArray.forEach(wire => { - // wire.hitStrokeWidth(0); - wire.strokeWidth(2); - }); - // layer.toggleHitCanvas(); - stage.draw(); - ctrlIsPressed = false; - } - }) } } export function deleteProgramNode(e, layer, stage) { let node = e.target.getParent(); + if (node.customClass && node.customClass.isDeletable === false) return; + deleteNodeByGroup(node, stage); +} + +export function deleteNodeByGroup(node, stage) { for (let each of node.customClass.execInPins) { let len = each.wire.length; for (let i = 0; i < len; i++) { @@ -73,11 +34,8 @@ export function deleteProgramNode(e, layer, stage) { if (each.wire[0]) { deleteWire(each.wire[0]); } } } - // console.log(e.target.getParent()); - e.target.getParent().destroy(); - let wireLayer = stage.findOne('#wireLayer'); + node.destroy(); stage.draw(); - } export function deleteWire(aWire) { @@ -140,6 +98,22 @@ export function deleteWire(aWire) { } ); } + if (lineClone._mismatchDragCleanup) { + lineClone._mismatchDragCleanup(); + lineClone._mismatchDragCleanup = null; + } + if (lineClone._mismatchIndicator) { + lineClone._mismatchIndicator.destroy(); + lineClone._mismatchIndicator = null; + } + if (lineClone._closeDragCleanup) { + lineClone._closeDragCleanup(); + lineClone._closeDragCleanup = null; + } + if (lineClone._closeIndicator) { + lineClone._closeIndicator.destroy(); + lineClone._closeIndicator = null; + } lineClone.destroy(); } diff --git a/javascript/DragAndDrop/DragAndDrop.js b/src/js/editor/dragAndDrop.js similarity index 100% rename from javascript/DragAndDrop/DragAndDrop.js rename to src/js/editor/dragAndDrop.js diff --git a/src/js/editor/nodeGroup.js b/src/js/editor/nodeGroup.js new file mode 100644 index 0000000..b5dce85 --- /dev/null +++ b/src/js/editor/nodeGroup.js @@ -0,0 +1,402 @@ +import { colorMap } from '../core/colorMap.js'; +import { tabManager } from './tabManager.js'; + +let placeLocation = function (location, stage) { + return { + x: (location.x - stage.x()) / stage.scaleX(), + y: (location.y - stage.y()) / stage.scaleY() + }; +} + +const MIN_GROUP_SIZE = 30; +const HANDLE_SIZE = 8; +const TITLE_HEIGHT = 24; +const MIN_RESIZE_W = 60; +const MIN_RESIZE_H = 50; + +function createHandles(bodyRect, titleBar, titleText, grp, layer, stage, closeBtn) { + const handles = []; + const corners = ['tl', 'tr', 'bl', 'br']; + + for (const corner of corners) { + const handle = new Konva.Rect({ + width: HANDLE_SIZE, + height: HANDLE_SIZE, + fill: colorMap['GroupHandle'], + stroke: colorMap['GroupHandleBorder'], + strokeWidth: 1, + draggable: true, + name: 'groupResizeHandle', + }); + const cursor = (corner === 'tl' || corner === 'br') ? 'nwse-resize' : 'nesw-resize'; + handle.on('mouseenter', () => { document.body.style.cursor = cursor; }); + handle.on('mouseleave', () => { document.body.style.cursor = 'default'; }); + + handle.on('dragmove', () => { + const hx = handle.x(); + const hy = handle.y(); + let x = bodyRect.x(); + let y = bodyRect.y(); + let w = bodyRect.width(); + let h = bodyRect.height(); + + if (corner === 'tl') { + const right = x + w; + const bottom = y + h; + x = Math.min(hx, right - MIN_RESIZE_W); + y = Math.min(hy, bottom - MIN_RESIZE_H); + w = right - x; + h = bottom - y; + } else if (corner === 'tr') { + const bottom = y + h; + w = Math.max(hx + HANDLE_SIZE - x, MIN_RESIZE_W); + y = Math.min(hy, bottom - MIN_RESIZE_H); + h = bottom - y; + } else if (corner === 'bl') { + const right = x + w; + x = Math.min(hx, right - MIN_RESIZE_W); + w = right - x; + h = Math.max(hy + HANDLE_SIZE - y, MIN_RESIZE_H); + } else { + w = Math.max(hx + HANDLE_SIZE - x, MIN_RESIZE_W); + h = Math.max(hy + HANDLE_SIZE - y, MIN_RESIZE_H); + } + + bodyRect.x(x); + bodyRect.y(y); + bodyRect.width(w); + bodyRect.height(h); + titleBar.x(x); + titleBar.y(y); + titleBar.width(w); + titleText.x(x + 6); + titleText.y(y + 4); + titleText.width(w - 12); + if (closeBtn) { + closeBtn.x(x + w - 16 - 4); + closeBtn.y(y + Math.round((TITLE_HEIGHT - 16) / 2)); + } + positionHandles(handles, bodyRect); + layer.batchDraw(); + }); + + handle.on('dragstart', (e) => { e.cancelBubble = true; }); + handle.on('dragend', (e) => { e.cancelBubble = true; }); + + handles.push({ rect: handle, corner }); + grp.add(handle); + } + + positionHandles(handles, bodyRect); + return handles; +} + +function positionHandles(handles, bodyRect) { + const x = bodyRect.x(); + const y = bodyRect.y(); + const w = bodyRect.width(); + const h = bodyRect.height(); + const half = HANDLE_SIZE / 2; + + for (const h_obj of handles) { + switch (h_obj.corner) { + case 'tl': h_obj.rect.position({ x: x - half, y: y - half }); break; + case 'tr': h_obj.rect.position({ x: x + w - half, y: y - half }); break; + case 'bl': h_obj.rect.position({ x: x - half, y: y + h - half }); break; + case 'br': h_obj.rect.position({ x: x + w - half, y: y + h - half }); break; + } + } +} + +let activeEditState = null; +let groupInputInitialized = false; + +function initGroupInput() { + if (groupInputInitialized) return; + groupInputInitialized = true; + const htmlInput = document.getElementById('group-name-ip'); + + htmlInput.addEventListener('blur', () => { + if (!activeEditState) return; + const { titleText, grp, layer } = activeEditState; + const val = htmlInput.value.trim() || 'Group'; + titleText.text(val); + grp._groupName = val; + titleText.visible(true); + htmlInput.style.display = 'none'; + htmlInput.value = ''; + activeEditState = null; + layer.batchDraw(); + }); + + htmlInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter') htmlInput.blur(); + }); +} + +function setupTitleEditing(titleText, titleBar, grp, stage, layer) { + initGroupInput(); + const htmlInput = document.getElementById('group-name-ip'); + + function startEdit() { + if (activeEditState) htmlInput.blur(); + activeEditState = { titleText, grp, layer }; + htmlInput.value = titleText.text(); + const stageRect = stage.getContainer().getBoundingClientRect(); + const borderLeft = parseInt(getComputedStyle(stage.getContainer()).borderLeftWidth) || 0; + const borderTop = parseInt(getComputedStyle(stage.getContainer()).borderTopWidth) || 0; + const absPos = titleText.getAbsolutePosition(); + htmlInput.style.left = (stageRect.x + borderLeft + absPos.x) + 'px'; + htmlInput.style.top = (stageRect.y + borderTop + absPos.y) + 'px'; + htmlInput.style.transform = `scale(${stage.scaleX()})`; + htmlInput.style.display = 'inline-block'; + htmlInput.focus(); + htmlInput.select(); + titleText.visible(false); + layer.batchDraw(); + } + + titleBar.on('dblclick', startEdit); + titleText.on('dblclick', startEdit); +} + +function findContainedNodes(grp, layer) { + const bodyRect = grp.find('Rect')[0]; + const gx = grp.x() + bodyRect.x(); + const gy = grp.y() + bodyRect.y(); + const gw = bodyRect.width(); + const gh = bodyRect.height(); + const nodes = []; + layer.find('.aProgramNodeGroup').forEach((node) => { + const nx = node.x(); + const ny = node.y(); + if (nx >= gx && ny >= gy && nx <= gx + gw && ny <= gy + gh) { + nodes.push(node); + } + }); + return nodes; +} + +function setupGroupDrag(grp, layer, stage) { + let containedNodes = []; + let nodeOffsets = []; + let wireLayer = null; + + grp.on('dragstart', (e) => { + e.cancelBubble = true; + wireLayer = tabManager.getActiveWireLayer(); + containedNodes = findContainedNodes(grp, layer); + nodeOffsets = containedNodes.map((node) => ({ + node, + offsetX: node.x() - grp.x(), + offsetY: node.y() - grp.y(), + })); + }); + + grp.on('dragmove', (e) => { + e.cancelBubble = true; + for (const entry of nodeOffsets) { + entry.node.position({ + x: grp.x() + entry.offsetX, + y: grp.y() + entry.offsetY, + }); + entry.node.fire('dragmove'); + } + layer.batchDraw(); + if (wireLayer) wireLayer.batchDraw(); + }); + + grp.on('dragend', (e) => { + e.cancelBubble = true; + grp.moveToBottom(); + containedNodes = []; + nodeOffsets = []; + layer.batchDraw(); + if (wireLayer) wireLayer.batchDraw(); + }); +} + +export function createNodeGroup(position, width, height, name, layer, stage) { + const grp = new Konva.Group({ + x: position.x, + y: position.y, + draggable: true, + name: 'aNodeGroup', + }); + grp._groupName = name || 'Group'; + + const bodyRect = new Konva.Rect({ + x: 0, + y: 0, + width: width, + height: height, + fill: colorMap['GroupBody'], + stroke: colorMap['GroupBorder'], + strokeWidth: 1.5, + cornerRadius: 6, + listening: false, + }); + + const titleBar = new Konva.Rect({ + x: 0, + y: 0, + width: width, + height: TITLE_HEIGHT, + fill: colorMap['GroupTitleBar'], + cornerRadius: [6, 6, 0, 0], + }); + + const titleText = new Konva.Text({ + x: 6, + y: 4, + text: grp._groupName, + fontSize: 13, + fontFamily: 'Verdana', + fontStyle: 'bold', + fill: colorMap['GroupTitleText'], + width: width - 12, + height: TITLE_HEIGHT, + listening: true, + }); + + const btnSize = 16; + const closeBtn = new Konva.Group({ + x: width - btnSize - 4, + y: Math.round((TITLE_HEIGHT - btnSize) / 2), + visible: false, + }); + const btnBg = new Konva.Rect({ + width: btnSize, + height: btnSize, + fill: 'rgba(0,0,0,0.5)', + cornerRadius: 3, + }); + const btnText = new Konva.Text({ + text: '\u00D7', + fontSize: 14, + fontFamily: 'Verdana', + fill: '#fff', + width: btnSize, + height: btnSize, + align: 'center', + verticalAlign: 'middle', + }); + closeBtn.add(btnBg); + closeBtn.add(btnText); + closeBtn.on('mouseenter', () => { + btnBg.fill('rgba(200,50,50,0.85)'); + document.body.style.cursor = 'pointer'; + layer.batchDraw(); + }); + closeBtn.on('mouseleave', () => { + btnBg.fill('rgba(0,0,0,0.5)'); + document.body.style.cursor = 'default'; + layer.batchDraw(); + }); + closeBtn.on('mousedown', (e) => { e.cancelBubble = true; }); + closeBtn.on('click', (e) => { + e.cancelBubble = true; + document.body.style.cursor = 'default'; + grp.destroy(); + layer.batchDraw(); + }); + + grp.add(bodyRect); + grp.add(titleBar); + grp.add(titleText); + grp.add(closeBtn); + + grp.on('mouseenter', () => { closeBtn.visible(true); layer.batchDraw(); }); + grp.on('mouseleave', () => { closeBtn.visible(false); layer.batchDraw(); }); + + const handles = createHandles(bodyRect, titleBar, titleText, grp, layer, stage, closeBtn); + + setupTitleEditing(titleText, titleBar, grp, stage, layer); + setupGroupDrag(grp, layer, stage); + + layer.add(grp); + grp.moveToBottom(); + layer.batchDraw(); + + return grp; +} + +export function getGroupsData(layer) { + const groups = []; + layer.find('.aNodeGroup').forEach((grp) => { + const bodyRect = grp.find('Rect')[0]; + groups.push({ + position: { x: grp.x(), y: grp.y() }, + width: bodyRect.width(), + height: bodyRect.height(), + name: grp._groupName || 'Group', + }); + }); + return groups; +} + +export function enableNodeGroups(stage) { + let isDrawing = false; + let startX = 0; + let startY = 0; + let previewRect = null; + + stage.on('mousedown', (e) => { + if (!e.evt.shiftKey || e.target !== stage || e.evt.button !== 0) return; + + const layer = tabManager.getActiveLayer(); + isDrawing = true; + const pos = placeLocation(stage.getPointerPosition(), stage); + startX = pos.x; + startY = pos.y; + + previewRect = new Konva.Rect({ + x: startX, + y: startY, + width: 0, + height: 0, + fill: colorMap['GroupPreview'], + stroke: colorMap['GroupPreviewBorder'], + strokeWidth: 1.5, + dash: [6, 3], + cornerRadius: 6, + }); + layer.add(previewRect); + layer.batchDraw(); + }); + + stage.on('mousemove', () => { + if (!isDrawing || !previewRect) return; + + const layer = tabManager.getActiveLayer(); + const pos = placeLocation(stage.getPointerPosition(), stage); + previewRect.setAttrs({ + x: Math.min(startX, pos.x), + y: Math.min(startY, pos.y), + width: Math.abs(pos.x - startX), + height: Math.abs(pos.y - startY), + }); + layer.batchDraw(); + }); + + stage.on('mouseup', (e) => { + if (!isDrawing || !previewRect) return; + isDrawing = false; + + const layer = tabManager.getActiveLayer(); + const w = previewRect.width(); + const h = previewRect.height(); + const x = previewRect.x(); + const y = previewRect.y(); + + previewRect.destroy(); + previewRect = null; + + if (w < MIN_GROUP_SIZE || h < MIN_GROUP_SIZE) { + layer.batchDraw(); + return; + } + + createNodeGroup({ x, y }, w, h, 'Group', layer, stage); + }); +} diff --git a/src/js/editor/orphanOverlay.js b/src/js/editor/orphanOverlay.js new file mode 100644 index 0000000..1e99a61 --- /dev/null +++ b/src/js/editor/orphanOverlay.js @@ -0,0 +1,85 @@ +import { deleteNodeByGroup } from './deleteHandler.js'; + +/** + * Apply the same red overlay and "Deleted Var" / "Deleted Func" indicator used + * when a variable or function is deleted but nodes still reference it. + * @param {Konva.Group} grp - The program node group (must have customClass.bodyRect) + * @param {Konva.Layer} layer - Layer for draw() + * @param {Konva.Stage} stage - Stage for deleteNodeByGroup + * @param {string} [label='Deleted Var'] - Label text (e.g. 'Deleted Func' for call nodes) + */ +export function applyOrphanOverlay(grp, layer, stage, label = 'Deleted Var') { + const cc = grp.customClass; + if (!cc) return; + cc.isOrphaned = true; + + const bodyRect = cc.bodyRect; + const overlay = new Konva.Rect({ + x: bodyRect.x(), + y: bodyRect.y(), + width: bodyRect.width(), + height: bodyRect.height(), + fill: 'rgba(255, 0, 0, 0.25)', + cornerRadius: 5, + listening: false, + }); + grp.add(overlay); + + const boxWidth = 120; + const boxHeight = 36; + const boxX = bodyRect.width() / 2 - boxWidth / 2; + const boxY = -boxHeight - 5; + + const indicatorGrp = new Konva.Group({ x: boxX, y: boxY }); + + const bg = new Konva.Rect({ + width: boxWidth, + height: boxHeight, + fill: 'rgba(30, 0, 0, 0.85)', + cornerRadius: 3, + stroke: '#f44', + strokeWidth: 1, + }); + indicatorGrp.add(bg); + + const labelText = new Konva.Text({ + text: label, + fontSize: 9, + fontFamily: 'Verdana', + fill: '#f88', + width: boxWidth, + align: 'center', + y: 3, + }); + indicatorGrp.add(labelText); + + const removeBtn = new Konva.Text({ + text: 'Remove', + fontSize: 10, + fontFamily: 'Verdana', + fill: '#fff', + width: boxWidth, + align: 'center', + y: 18, + }); + removeBtn.on('mouseenter', () => { + removeBtn.fill('#f44'); + document.body.style.cursor = 'pointer'; + if (layer) layer.draw(); + }); + removeBtn.on('mouseleave', () => { + removeBtn.fill('#fff'); + document.body.style.cursor = 'default'; + if (layer) layer.draw(); + }); + removeBtn.on('click', (e) => { + e.cancelBubble = true; + document.body.style.cursor = 'default'; + deleteNodeByGroup(grp, stage); + }); + indicatorGrp.add(removeBtn); + + grp.add(indicatorGrp); + cc._orphanOverlay = overlay; + cc._orphanIndicator = indicatorGrp; +} diff --git a/javascript/SelectionBox/SelectionBox.js b/src/js/editor/selectionBox.js similarity index 97% rename from javascript/SelectionBox/SelectionBox.js rename to src/js/editor/selectionBox.js index 32061ef..3a1cd9b 100644 --- a/javascript/SelectionBox/SelectionBox.js +++ b/src/js/editor/selectionBox.js @@ -1,4 +1,4 @@ -import { setLocationOfNode } from '../setLocationOfNode/setLocationOfNode.js' +import { setLocationOfNode } from '../nodes/nodePosition.js' export var SelectionBox = { setSelectionBox: function (layer, stage) { this.selectionRectangle = new Konva.Rect({ diff --git a/src/js/editor/tabManager.js b/src/js/editor/tabManager.js new file mode 100644 index 0000000..096a4bb --- /dev/null +++ b/src/js/editor/tabManager.js @@ -0,0 +1,288 @@ +import { showAlert, showConfirm } from '../ui/dialogs.js'; + +const JS_RESERVED = new Set([ + 'break','case','catch','continue','debugger','default','delete','do', + 'else','finally','for','function','if','in','instanceof','new','return', + 'switch','this','throw','try','typeof','var','void','while','with', + 'class','const','enum','export','extends','import','super','implements', + 'interface','let','package','private','protected','public','static','yield', + 'await','async','null','undefined','true','false','NaN','Infinity', +]); + +export function isValidFunctionName(name) { + if (!name || name.length === 0) return false; + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) return false; + if (JS_RESERVED.has(name)) return false; + return true; +} + +class TabManager { + constructor() { + this._stage = null; + this._tabs = new Map(); + this._activeTabId = null; + this._nextId = 1; + this._listeners = {}; + this._sharedDragLayer = null; + } + + init(stage, mainLayer, mainWireLayer, dragLayer) { + this._stage = stage; + this._sharedDragLayer = dragLayer; + const mainTab = { + id: 'main', + name: 'Main', + type: 'main', + layer: mainLayer, + wireLayer: mainWireLayer, + inputParams: [], + outputParams: [], + beginNodeId: null, + returnNodeId: null, + variables: [], + }; + this._tabs.set('main', mainTab); + this._activeTabId = 'main'; + this._renderTabBar(); + } + + getActiveTab() { return this._tabs.get(this._activeTabId); } + getActiveTabId() { return this._activeTabId; } + getActiveLayer() { return this._tabs.get(this._activeTabId).layer; } + getActiveWireLayer() { return this._tabs.get(this._activeTabId).wireLayer; } + getDragLayer() { return this._sharedDragLayer; } + getStage() { return this._stage; } + + getTab(id) { return this._tabs.get(id); } + getAllTabs() { return [...this._tabs.values()]; } + getAllFunctionTabs() { return [...this._tabs.values()].filter(t => t.type === 'function'); } + getAllSavedFunctionTabs() { return [...this._tabs.values()].filter(t => t.type === 'function' && t.saved); } + + createTab(name) { + if (!isValidFunctionName(name)) { + showAlert('Invalid function name. Use letters, numbers, and underscores. Must start with a letter or underscore.'); + return null; + } + if (this._isDuplicateName(name)) { + showAlert(`Function "${name}" already exists.`); + return null; + } + + const id = `func_${this._nextId++}`; + const layer = new Konva.Layer({ id: `layer_${id}` }); + const wireLayer = new Konva.Layer({ id: `wireLayer_${id}` }); + + this._stage.add(wireLayer); + this._stage.add(layer); + layer.moveToBottom(); + + layer.hide(); + wireLayer.hide(); + + const tab = { + id, + name, + type: 'function', + layer, + wireLayer, + inputParams: [], + outputParams: [], + beginNodeId: null, + returnNodeId: null, + variables: [], + docString: '', + saved: null, + }; + + this._tabs.set(id, tab); + this._renderTabBar(); + this.switchTab(id); + this._emit('tabCreated', tab); + return tab; + } + + switchTab(id) { + if (!this._tabs.has(id) || id === this._activeTabId) return; + + const prevTab = this._tabs.get(this._activeTabId); + prevTab.layer.hide(); + prevTab.wireLayer.hide(); + + this._activeTabId = id; + const newTab = this._tabs.get(id); + newTab.layer.show(); + newTab.wireLayer.show(); + + this._stage.draw(); + this._updateTabBarSelection(); + this._emit('tabSwitched', { from: prevTab, to: newTab }); + } + + renameTab(id, newName) { + if (id === 'main') return false; + if (!isValidFunctionName(newName)) { + showAlert('Invalid function name.'); + return false; + } + if (this._isDuplicateName(newName, id)) { + showAlert(`Function "${newName}" already exists.`); + return false; + } + + const tab = this._tabs.get(id); + if (!tab) return false; + const oldName = tab.name; + tab.name = newName; + if (tab.saved) tab.saved.name = newName; + this._renderTabBar(); + this._emit('tabRenamed', { tab, oldName, newName }); + return true; + } + + closeTab(id) { + if (id === 'main') return false; + const tab = this._tabs.get(id); + if (!tab) return false; + + if (this._activeTabId === id) { + this.switchTab('main'); + } + + tab.layer.destroy(); + tab.wireLayer.destroy(); + this._tabs.delete(id); + + this._renderTabBar(); + this._emit('tabClosed', tab); + this._stage.draw(); + return true; + } + + getFunctionDefinition(tabId) { + const tab = this._tabs.get(tabId); + if (!tab || tab.type !== 'function') return null; + return { + name: tab.name, + inputs: [...tab.inputParams], + outputs: [...tab.outputParams], + }; + } + + saveFunction(tabId) { + const tab = this._tabs.get(tabId); + if (!tab || tab.type !== 'function') return; + tab.saved = { + name: tab.name, + inputParams: tab.inputParams.map(p => ({ ...p })), + outputParams: tab.outputParams.map(p => ({ ...p })), + docString: tab.docString || '', + }; + this._emit('functionSaved', tab); + } + + on(event, fn) { + if (!this._listeners[event]) this._listeners[event] = []; + this._listeners[event].push(fn); + } + + off(event, fn) { + if (!this._listeners[event]) return; + this._listeners[event] = this._listeners[event].filter(f => f !== fn); + } + + _emit(event, data) { + if (this._listeners[event]) { + this._listeners[event].forEach(fn => fn(data)); + } + } + + _isDuplicateName(name, excludeId) { + for (const [id, tab] of this._tabs) { + if (id !== excludeId && tab.name === name) return true; + } + return false; + } + + _renderTabBar() { + const tabList = document.getElementById('tab-list'); + if (!tabList) return; + tabList.innerHTML = ''; + + for (const [id, tab] of this._tabs) { + const tabEl = document.createElement('div'); + tabEl.className = 'tab-item'; + if (id === this._activeTabId) tabEl.classList.add('tab-active'); + tabEl.dataset.tabId = id; + + const nameEl = document.createElement('span'); + nameEl.className = 'tab-name'; + nameEl.textContent = tab.name; + tabEl.appendChild(nameEl); + + if (tab.type === 'function') { + nameEl.addEventListener('dblclick', (e) => { + e.stopPropagation(); + this._startRename(tabEl, tab); + }); + + const closeBtn = document.createElement('span'); + closeBtn.className = 'tab-close'; + closeBtn.textContent = '\u00D7'; + closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + showConfirm( + `Close function "${tab.name}"? The function definition and its contents will be removed.`, + () => { this.closeTab(id); } + ); + }); + tabEl.appendChild(closeBtn); + } + + tabEl.addEventListener('click', () => { + this.switchTab(id); + }); + + tabList.appendChild(tabEl); + } + } + + _updateTabBarSelection() { + const tabList = document.getElementById('tab-list'); + if (!tabList) return; + tabList.querySelectorAll('.tab-item').forEach(el => { + el.classList.toggle('tab-active', el.dataset.tabId === this._activeTabId); + }); + } + + _startRename(tabEl, tab) { + const nameEl = tabEl.querySelector('.tab-name'); + const input = document.createElement('input'); + input.type = 'text'; + input.className = 'tab-rename-input'; + input.value = tab.name; + + nameEl.replaceWith(input); + input.focus(); + input.select(); + + let finished = false; + const doFinish = () => { + if (finished) return; + finished = true; + const newName = input.value.trim(); + if (newName && newName !== tab.name) { + this.renameTab(tab.id, newName); + } else { + this._renderTabBar(); + } + }; + + input.addEventListener('blur', doFinish); + input.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); input.blur(); } + if (e.key === 'Escape') { input.value = tab.name; input.blur(); } + }); + } +} + +export const tabManager = new TabManager(); diff --git a/src/js/nodes/nodeFactory.js b/src/js/nodes/nodeFactory.js new file mode 100644 index 0000000..5417a1e --- /dev/null +++ b/src/js/nodes/nodeFactory.js @@ -0,0 +1,977 @@ +import { InputBox } from './nodeInputBox.js' +import { colorMap } from '../core/colorMap.js' +import { setLocationOfNode } from './nodePosition.js'; +import { buildNodeDescription, hasType } from '../registry/index.js'; +import { deleteNodeByGroup, deleteWire } from '../editor/deleteHandler.js'; +import { tabManager } from '../editor/tabManager.js'; +import { addConnectionWire } from './wiring.js'; +import { applyMismatchToWire, isTypeCompatible } from '../utils/wireMismatch.js'; + +let placeLocation = function (location) { + //"this" is stage + return { + x: (location.x - this.x()) / this.scaleX(), + y: (location.y - this.y()) / this.scaleY() + }; +} +export var Nodes = { + countNodes: 0, + getExecPin: function (inType, helper, layer) { + // let pointsExecIn = [0, 0, -14, -7, -14, 7]; + // let pointsExecOut = [] + let pin = new Konva.Line({ + points: [0, 0, -14, -7, -14, 7], + stroke: 'white', + strokeWidth: 2, + hitStrokeWidth: 10, + closed: true, + helper: helper, + name: 'pin', + offsetX: (inType) ? -14 : 0, + pinType: (inType) ? 'exec-in' : 'exec-out', + pinDataType: null, + fill: '', + }); + pin.on("mouseenter", () => { + pin.strokeWidth(4); + layer.draw(); + }); + pin.on("mouseleave", () => { + pin.strokeWidth(2); + layer.draw(); + }); + pin.on("wireremoved", (e) => { + if (e.isPinEmpty) { + pin.fill('transparent'); + } + }); + pin.on("wireconnected", (e) => { + pin.fill("white"); + }); + pin.on("wiringstart", (e) => { + pin.fill("white"); + layer.draw(); + }); + return pin; + }, + getRectBlock: function (height, width) { + let rect = new Konva.Rect({ + height: height, + width: width, + // fill: colorMap['MainBox'], + opacity: 0.8, + cornerRadius: 5, + shadowColor: 'black', + shadowBlur: 15, + shadowOffset: { x: 15, y: 15 }, + shadowOpacity: 0.5, + fillLinearGradientStartPoint: { x: 0, y: 0 }, + fillLinearGradientEndPoint: { x: width, y: height }, + fillLinearGradientColorStops: [0, colorMap['MainBoxGradient']['start'], 1, colorMap['MainBoxGradient']['end']], + // fillLinearGradientColorStops: [0, '#12100e', 1, '#2b4162'], + + // strokeWidth: [10, 10, 110, 0], + }); + return rect; + }, + getInputPin: function (inType, helper, type, layer) { + let pin = new Konva.Circle({ + radius: 7, + stroke: colorMap[type], + strokeWidth: 2, + hitStrokeWidth: 10, + name: 'pin', + pinType: (inType) ? 'inp' : 'outp', + pinDataType: type, + offsetX: (inType) ? -7 : 7, + helper: helper, + fill: '', + }); + pin.on("mouseenter", () => { + pin.strokeWidth(4); + layer.draw(); + }); + pin.on("mouseleave", () => { + pin.strokeWidth(2); + layer.draw(); + }); + pin.on("wireremoved", (e) => { + if (e.isPinEmpty) { + pin.fill('transparent'); + } + }); + pin.on("wireconnected", (e) => { + pin.fill(`${colorMap[type]}`); + }); + pin.on("wiringstart", (e) => { + pin.fill(`${colorMap[type]}`); + layer.draw(); + }); + return pin; + }, + // getOutputPin: function(){ + // let pin = new Konva.Circle({ + // radius: 7, + // stroke: 'yellow', + // strokeWidth: '2', + // name: 'pin', + // pinType: 'outp', + // }); + // return pin; + // }, + getLabel: function (text, size, width, color) { + let rect = new Konva.Rect({ + width: width, + height: size + 3, + fill: colorMap[color], + cornerRadius: [5, 5, 0, 0], + // fillLinearGradientStartPoint: { x: 0, y: 0 }, + // fillLinearGradientEndPoint: { x: width, y: size + 3 }, + // fillLinearGradientColorStops: [0, colorMap[color], 1, 'rgba(0, 0, 0, 0)'], + // fillRadialGradientStartPoint: {x: 0, y: 0}, + // fillRadialGradientEndPoint: { x: 30, y: 0 }, + // fillRadialGradientColorStops: [0, colorMap[color], 1, '#2d3436'], + // fillRadialGradientStartRadius: size / 3, + // fillRadialGradientEndRadius: 100, + + // fillLinearGradientColorStops: [0, '#9e768f', 1, '#ff4e00'], + + // #ec9f05 #ff4e00 + }); + let label = new Konva.Text({ + text: text, + fontSize: size - 5, + fontFamily: 'Verdana', + fill: colorMap['MainLabel'], + width: width, + // height: size + 3, + y: 2, + align: 'left', + padding: 3, + // padding: 10 + }); + return { bg: rect, text: label }; + }, + getPinCounts: function (nodeDescription) { + let inputPinCounts = 0; + let outputPinCounts = 0; + if (nodeDescription.execIn) + inputPinCounts++; + if (nodeDescription.inputs) { + inputPinCounts += Object.keys(nodeDescription.inputs).length; + } + + //For outputs + if (nodeDescription.execOut) { + outputPinCounts += Object.keys(nodeDescription.execOut).length; + } + if (nodeDescription.outputs) { + outputPinCounts += Object.keys(nodeDescription.outputs).length; + + } + return Math.max(inputPinCounts, outputPinCounts); + }, + // getEditableTextBox: function (type, stage, index) { + // let rect = new Konva.Rect({ + // width: (type == 'Boolean') ? 14 : 50, + // height: 14, + // stroke: colorMap[type], + // strokeWidth: 1, + // }); + // return rect; + // }, + getInputLabel: function (labelText, isInput) { + let text = new Konva.Text({ + // width: 40, + height: 14, + text: labelText, + fontSize: 11, + fontFamily: 'Verdana', + fill: colorMap['Text'], + }); + if (isInput) + text.offsetX(0); + else + text.offsetX(text.width()); + // text.off() + return text; + }, + getExecOutTitle: function (labelText) { + let text = new Konva.Text({ + height: 14, + fontSize: 11, + text: labelText, + fontFamily: 'Verdana', + fill: "white", + }); + text.offsetX(text.width()); + return text; + }, + optimizeDrag: function (grp, stage, layer) { + function moveWireToLayer(aWire, targetLayer) { + aWire.moveTo(targetLayer); + if (aWire._closeIndicator) aWire._closeIndicator.moveTo(targetLayer); + } + grp.on('dragstart', () => { + let dragLayer = tabManager.getDragLayer(); + let wireLayer = tabManager.getActiveWireLayer(); + grp.moveTo(dragLayer); + for (let each of grp.customClass.execInPins) { + for (let aWire of each.wire) { + moveWireToLayer(aWire, dragLayer); + } + } + for (let each of grp.customClass.execOutPins) { + if (each.wire) + moveWireToLayer(each.wire, dragLayer); + } + for (let each of grp.customClass.inputPins) { + if (each.wire) + moveWireToLayer(each.wire, dragLayer); + } + for (let each of grp.customClass.outputPins) { + for (let aWire of each.wire) { + moveWireToLayer(aWire, dragLayer); + } + } + wireLayer.draw(); + dragLayer.draw(); + layer.draw(); + }) + grp.on('dragend', () => { + let dragLayer = tabManager.getDragLayer(); + let wireLayer = tabManager.getActiveWireLayer(); + grp.moveTo(layer); + for (let each of grp.customClass.execInPins) { + for (let aWire of each.wire) { + moveWireToLayer(aWire, wireLayer); + } + } + for (let each of grp.customClass.execOutPins) { + if (each.wire) + moveWireToLayer(each.wire, wireLayer); + } + for (let each of grp.customClass.inputPins) { + if (each.wire) + moveWireToLayer(each.wire, wireLayer); + } + for (let each of grp.customClass.outputPins) { + for (let aWire of each.wire) { + moveWireToLayer(aWire, wireLayer); + } + } + wireLayer.draw(); + dragLayer.draw(); + layer.draw(); + }); + }, + getBorderRect: function (height, width) { + let rect = new Konva.Rect({ + height: height, + width: width, + fill: 'transparent', + stroke: '#dbd8e3', + strokeWidth: 0, + cornerRadius: 5, + name: 'borderbox', + }); + rect.off('click mouseover mouseenter mouseleave'); + return rect; + }, + ProgramNode: class { + constructor(nodeDescription, location, layer, stage) { + + + + this.grp = new Konva.Group({ + draggable: true, + name: "aProgramNodeGroup", + }); + if (nodeDescription.nodeTitle == 'Begin') { + this.grp.id('Begin'); + } else if (nodeDescription.nodeTitle == 'FunctionBegin') { + this.grp.id('FunctionBegin'); + } else if (nodeDescription.nodeTitle == 'Return') { + this.grp.id('Return'); + } + this.isDeletable = nodeDescription.isDeletable !== false; + this.grp.customClass = this; + // this.grp.on('dblclick', (e) => { + // console.table(e.currentTarget.customClass); + // }) + this.nodeDescription = nodeDescription; + let relativePosition = placeLocation.bind(stage); + let maxOfPinsOnEitherSide = Nodes.getPinCounts(nodeDescription); + let height = maxOfPinsOnEitherSide * 50 + 15; + let width = nodeDescription.colums * 15; + this.grp.position(relativePosition(location)); + let rect = Nodes.getRectBlock(height, width); + this.bodyRect = rect; + this.grp.add(rect); + let borderRect = Nodes.getBorderRect(height, width); + let titleLabel = Nodes.getLabel(nodeDescription.nodeTitle, 20, width, nodeDescription.color); + this.titleBg = titleLabel.bg; + this.titleText = titleLabel.text; + this.grp.add(titleLabel.bg); + this.grp.add(titleLabel.text); + this.grp.add(borderRect); + + let closeBtn = null; + if (nodeDescription.nodeTitle !== 'Begin' && nodeDescription.isDeletable !== false) { + const btnSize = 16; + const btnX = width - btnSize - 4; + const btnY = Math.round((23 - btnSize) / 2); + closeBtn = new Konva.Group({ x: btnX, y: btnY, visible: false }); + const btnBg = new Konva.Rect({ + width: btnSize, + height: btnSize, + fill: 'rgba(0,0,0,0.5)', + cornerRadius: 3, + }); + const btnText = new Konva.Text({ + text: '\u00D7', + fontSize: 14, + fontFamily: 'Verdana', + fill: '#fff', + width: btnSize, + height: btnSize, + align: 'center', + verticalAlign: 'middle', + }); + closeBtn.add(btnBg); + closeBtn.add(btnText); + closeBtn.on('mouseenter', () => { + btnBg.fill('rgba(200,50,50,0.85)'); + document.body.style.cursor = 'pointer'; + layer.draw(); + }); + closeBtn.on('mouseleave', () => { + btnBg.fill('rgba(0,0,0,0.5)'); + document.body.style.cursor = 'default'; + layer.draw(); + }); + closeBtn.on('mousedown', (e) => { + e.cancelBubble = true; + }); + closeBtn.on('click', (e) => { + e.cancelBubble = true; + document.body.style.cursor = 'default'; + deleteNodeByGroup(this.grp, stage); + }); + this.grp.add(closeBtn); + } + + this.grp.on("mouseover", (e) => { + borderRect.strokeWidth(1); + if (closeBtn) closeBtn.visible(true); + layer.draw(); + }); + this.grp.on("mouseleave", (e) => { + borderRect.strokeWidth(0); + if (closeBtn) closeBtn.visible(false); + layer.draw(); + }); + this.grp.on('mousedown', (e) => { + rect.shadowBlur(25); + // rect.shadowOffset({ x: 25, y: 25 }); + layer.draw(); + }) + this.grp.on('mouseup', (e) => { + rect.shadowBlur(15); + // rect.shadowOffset({ x: 15, y: 15 }); + layer.draw(); + }) + /****/ + + Nodes.optimizeDrag(this.grp, stage, layer); + + /****/ + // titleLabel.offsetX(titleLabel.width() / 2); + let inputPinsPlaced = 0, outputPinsPlaced = 0; + this.execInPins = []; + if (nodeDescription.execIn == true) { + let execInPin = Nodes.getExecPin(true, 'exec-in-0', layer); + execInPin.position({ x: 7, y: 44 }); + if (nodeDescription.pinExecInId == null) { + execInPin.id(`${execInPin._id}`); + } + else { + execInPin.id(nodeDescription.pinExecInId); + } + this.nodeDescription.pinExecInId = execInPin.id(); + this.grp.add(execInPin); + let tmp = { + thisNode: execInPin, + wire: [], + } + this.execInPins.push(tmp); + inputPinsPlaced = 1; + } + + let X = nodeDescription.nodeTitle.split(" "); + this.type = { + isGetSet: (X[0] == 'Get' || X[0] == 'Set'), + typeOfNode: nodeDescription.nodeTitle, + } + this.execOutPins = []; + if (nodeDescription.execOut) { + Object.keys(nodeDescription.execOut).forEach((value, index) => { + let execOutPin = Nodes.getExecPin(false, `exec-out-${index}`, layer); + execOutPin.position({ x: width - 7, y: 44 + nodeDescription.execOut[value].outOrder * 39 }); + if (nodeDescription.execOut[value].pinExecOutId == null) { + execOutPin.id(`${execOutPin._id}`); + } + else { + execOutPin.id(nodeDescription.execOut[value].pinExecOutId); + } + this.nodeDescription.execOut[value].pinExecOutId = execOutPin.id(); + this.grp.add(execOutPin); + if (nodeDescription.execOut[value].execOutTitle) { + let exLabel = Nodes.getExecOutTitle(nodeDescription.execOut[value].execOutTitle); + exLabel.position({ x: width - 28, y: 44 + nodeDescription.execOut[value].outOrder * 39 - 4 }); + this.grp.add(exLabel); + } + let tmp = { + thisNode: execOutPin, + wire: null, + title: value.execOutTitle, + } + this.execOutPins.push(tmp); + outputPinsPlaced++; + }); + } + this.inputPins = []; + if (nodeDescription.inputs) { + Object.keys(nodeDescription.inputs).forEach((value, index) => { + let inputPin = Nodes.getInputPin(true, `inp-${index}`, nodeDescription.inputs[value].dataType, layer); + inputPin.position({ x: 7, y: 44 + 39 * inputPinsPlaced }); + if (nodeDescription.inputs[value].pinInId == null) { + inputPin.id(`${inputPin._id}`); + } + else { + inputPin.id(nodeDescription.inputs[value].pinInId); + } + this.nodeDescription.inputs[value].pinInId = inputPin.id(); + // iprect.position({ x: 28, y: 44 + 39 * inputPinsPlaced - 2 }); + let iprect = null; + let iplabel = Nodes.getInputLabel(nodeDescription.inputs[value].inputTitle, true); + iplabel.position({ x: 28, y: 44 + 39 * inputPinsPlaced - 4 }); + if (nodeDescription.inputs[value].isInputBoxRequired !== false) { + // console.log(nodeDescription.inputs, this.nodeDescription.inputs); + iprect = new InputBox(stage, layer, nodeDescription.inputs[value].dataType, this.grp, { x: 28, y: 44 + 39 * inputPinsPlaced - 2 }, colorMap, inputPin, iplabel, inputPinsPlaced, nodeDescription.inputs[value], this.nodeDescription.inputs[value]); + iplabel.position({ x: 28, y: 44 + 39 * inputPinsPlaced - 14 }); + } + this.grp.add(iplabel); + this.grp.add(inputPin); + // this.grp.add(iprect); + let tmp = { + thisNode: inputPin, + wire: null, + textBox: iprect, + value: null, + title: value.inputTitle, + } + this.inputPins.push(tmp); + inputPinsPlaced++; + }); + } + this.outputPins = []; + if (nodeDescription.outputs) { + Object.keys(nodeDescription.outputs).forEach((value, index) => { + let outputPin = Nodes.getInputPin(false, `out-${index}`, nodeDescription.outputs[value].dataType, layer); + outputPin.position({ x: width - 7, y: 44 + 39 * nodeDescription.outputs[value].outOrder }); + if (nodeDescription.outputs[value].pinOutId == null) { + outputPin.id(`${outputPin._id}`); + } + else { + outputPin.id(nodeDescription.outputs[value].pinOutId); + } + nodeDescription.outputs[value].pinOutId = outputPin.id(); + this.grp.add(outputPin); + let outLabel = Nodes.getInputLabel(nodeDescription.outputs[value].outputTitle, false); + outLabel.position({ x: width - 28, y: 44 + 39 * nodeDescription.outputs[value].outOrder - 4 }) + this.grp.add(outLabel); + let tmp = { + wire: [], + value: null, + title: value.outputTitle, + } + this.outputPins.push(tmp); + outputPinsPlaced++; + }) + }; + // this.grp.cache(); + layer.add(this.grp); + layer.draw(); + layer.draw(); + // console.log(JSON.parse(JSON.stringify(this.grp))); + } + }, + + + + + + CreateNode: function (type, location, layer, stage, isGetSet, dataType, defValue) { + let nodeDescription = null; + if (hasType(type)) { + nodeDescription = buildNodeDescription(type); + } else if (isGetSet === 'Set' || isGetSet === 'Get') { + nodeDescription = {}; + if (isGetSet == "Set") { + let defaultValueByType = { "Number": 0, "Boolean": true, "String": "'hello'", "Array": '[]' }; + nodeDescription.nodeTitle = type; + nodeDescription.execIn = true; + nodeDescription.pinExecInId = null; + nodeDescription.execOut = { execOut0: { execOutTitle: null, pinExecOutId: null, outOrder: 0 } }; + nodeDescription.inputs = { input0: { inputTitle: 'Value', dataType: dataType, defValue: defaultValueByType[dataType], pinInId: null } }; + nodeDescription.outputs = { output0: { outputTitle: 'Value(Ref)', dataType: dataType, pinOutId: null, outOrder: 1 } }; + nodeDescription.color = 'Func'; + nodeDescription.rows = 2; + nodeDescription.colums = 12; + } + if (isGetSet == "Get") { + nodeDescription.nodeTitle = type; + nodeDescription.outputs = { output0: { outputTitle: 'Value(Ref)', dataType: dataType, pinOutId: null, outOrder: 0 } }; + nodeDescription.color = 'Get'; + nodeDescription.rows = 2; + nodeDescription.colums = 10; + } + } + if (!nodeDescription) return; + new this.ProgramNode(nodeDescription, location, layer, stage); + }, + + buildFunctionBeginDescription: function (inputParams) { + const nd = { + nodeTitle: 'FunctionBegin', + execIn: false, + pinExecInId: null, + execOut: { execOut0: { execOutTitle: null, pinExecOutId: null, outOrder: 0 } }, + color: 'FunctionBegin', + rows: 2, + colums: 12, + isDeletable: false, + }; + if (inputParams && inputParams.length > 0) { + nd.outputs = {}; + inputParams.forEach((p, i) => { + nd.outputs[`output${i}`] = { + outputTitle: p.name, + dataType: p.dataType, + pinOutId: null, + outOrder: i + 1, + }; + }); + nd.rows = Math.max(2, inputParams.length + 1); + } + return nd; + }, + + buildReturnDescription: function (outputParams) { + const nd = { + nodeTitle: 'Return', + execIn: true, + pinExecInId: null, + color: 'Return', + rows: 2, + colums: 12, + isDeletable: false, + }; + if (outputParams && outputParams.length > 0) { + nd.inputs = {}; + outputParams.forEach((p, i) => { + nd.inputs[`input${i}`] = { + inputTitle: p.name, + dataType: p.dataType, + defValue: null, + pinInId: null, + isInputBoxRequired: false, + }; + }); + nd.rows = Math.max(2, outputParams.length + 1); + } + return nd; + }, + + CreateFunctionBeginNode: function (inputParams, location, layer, stage) { + const nd = this.buildFunctionBeginDescription(inputParams); + const node = new this.ProgramNode(nd, location, layer, stage); + node.grp.id('FunctionBegin'); + return node; + }, + + CreateReturnNode: function (outputParams, location, layer, stage) { + const nd = this.buildReturnDescription(outputParams); + const node = new this.ProgramNode(nd, location, layer, stage); + node.grp.id('Return'); + return node; + }, + + rebuildFunctionBeginNode: function (node, inputParams, layer, stage, wireLayer) { + const programNode = node; + const grp = programNode.grp; + const oldNd = programNode.nodeDescription; + const oldOutputs = oldNd.outputs ? Object.keys(oldNd.outputs).sort().map(k => oldNd.outputs[k]) : []; + + const savedOutputs = []; + for (let j = 0; j < programNode.outputPins.length; j++) { + const pin = programNode.outputPins[j]; + const wires = [...(Array.isArray(pin.wire) ? pin.wire : (pin.wire ? [pin.wire] : []))].filter(Boolean); + savedOutputs[j] = wires.map(w => { + const destPin = w.attrs.dest; + const oldType = oldOutputs[j]?.dataType; + deleteWire(w); + return { destPin, oldType }; + }); + } + + const savedExecOuts = []; + for (let j = 0; j < programNode.execOutPins.length; j++) { + const ep = programNode.execOutPins[j]; + if (ep.wire) { + const destPin = ep.wire.attrs.dest; + deleteWire(ep.wire); + savedExecOuts[j] = destPin; + } + } + + const pos = grp.position(); + const grpId = grp.id(); + grp.destroy(); + + const newNd = this.buildFunctionBeginDescription(inputParams); + const newNode = new this.ProgramNode(newNd, { + x: pos.x * stage.scaleX() + stage.x(), + y: pos.y * stage.scaleY() + stage.y() + }, layer, stage); + newNode.grp.id(grpId); + const newCC = newNode.grp.customClass; + + for (let j = 0; j < savedExecOuts.length; j++) { + if (!savedExecOuts[j]) continue; + const newExecOutPin = newCC.execOutPins[j]?.thisNode; + if (newExecOutPin) { + addConnectionWire(savedExecOuts[j], newExecOutPin, stage, 1, wireLayer); + } + } + + const outPins = newNode.grp.find('.pin').filter(p => p.attrs.pinType === 'outp'); + outPins.sort((a, b) => { + const ai = parseInt(String(a.attrs.helper || '').split('-')[1], 10) || 0; + const bi = parseInt(String(b.attrs.helper || '').split('-')[1], 10) || 0; + return ai - bi; + }); + + for (let j = 0; j < savedOutputs.length && j < (inputParams || []).length; j++) { + const newSrcPin = outPins[j]; + if (!newSrcPin) continue; + for (const { destPin, oldType } of savedOutputs[j]) { + addConnectionWire(destPin, newSrcPin, stage, 1, wireLayer); + const newType = (inputParams || [])[j]?.dataType; + if (!isTypeCompatible(newType, destPin.attrs.pinDataType)) { + const inpIdx = destPin.attrs.helper ? parseInt(String(destPin.attrs.helper).split('-')[1], 10) : 0; + const wireToMark = destPin.getParent().customClass?.inputPins?.[inpIdx]?.wire; + if (wireToMark) applyMismatchToWire(wireToMark, wireLayer, stage); + } + } + } + return newNode; + }, + + buildCallNodeDescription: function (funcName, inputParams, outputParams, docString) { + const nd = { + nodeTitle: `Call ${funcName}`, + execIn: true, + pinExecInId: null, + execOut: { execOut0: { execOutTitle: null, pinExecOutId: null, outOrder: 0 } }, + color: 'Call', + rows: 2, + colums: 14, + isCallFunction: true, + calledFunctionName: funcName, + docString: docString || '', + }; + const maxPins = Math.max( + (inputParams ? inputParams.length : 0), + (outputParams ? outputParams.length : 0) + ); + nd.rows = Math.max(2, maxPins + 1); + if (inputParams && inputParams.length > 0) { + nd.inputs = {}; + const defByType = { Number: 0, Boolean: true, String: "'hello'", Array: '[]' }; + inputParams.forEach((p, i) => { + const defVal = p.defValue != null ? p.defValue : defByType[p.dataType]; + nd.inputs[`input${i}`] = { + inputTitle: p.name, + dataType: p.dataType, + defValue: defVal, + pinInId: null, + isInputBoxRequired: true, + }; + }); + } + if (outputParams && outputParams.length > 0) { + nd.outputs = {}; + outputParams.forEach((p, i) => { + nd.outputs[`output${i}`] = { + outputTitle: p.name, + dataType: p.dataType, + pinOutId: null, + outOrder: i + 1, + }; + }); + } + return nd; + }, + + CreateCallNode: function (funcName, inputParams, outputParams, location, layer, stage, docString) { + const nd = this.buildCallNodeDescription(funcName, inputParams, outputParams, docString); + return new this.ProgramNode(nd, location, layer, stage); + }, + + updateCallNodesDocString: function (funcName, docString) { + const allTabs = tabManager.getAllTabs(); + for (const tab of allTabs) { + const layer = tab.layer; + if (!layer) continue; + layer.find('.aProgramNodeGroup').forEach((grp) => { + const cc = grp.customClass; + if (!cc || !cc.nodeDescription || !cc.nodeDescription.isCallFunction || cc.nodeDescription.calledFunctionName !== funcName) return; + cc.nodeDescription.docString = docString || ''; + }); + } + }, + + /** + * Update all Call nodes that call the given function with the current definition. + * Smart wire matching: preserve wires when params match by index; remove wires for + * deleted params; apply dashed mismatch when type changes. + */ + updateCallNodesToDefinition: function (funcName, inputParams, outputParams, docString) { + const allTabs = tabManager.getAllTabs(); + const stage = tabManager.getStage(); + const inputParamsArr = inputParams || []; + const outputParamsArr = outputParams || []; + + for (const tab of allTabs) { + const layer = tab.layer; + const wireLayer = tab.wireLayer; + if (!layer || !wireLayer) continue; + + const toUpdate = []; + layer.find('.aProgramNodeGroup').forEach((grp) => { + const cc = grp.customClass; + if (!cc || !cc.nodeDescription || !cc.nodeDescription.isCallFunction || cc.nodeDescription.calledFunctionName !== funcName) return; + if (cc.isOrphaned) return; + toUpdate.push({ grp, layer, wireLayer, programNode: cc }); + }); + + for (const { grp, layer, wireLayer, programNode } of toUpdate) { + const nd = programNode.nodeDescription; + const oldInputs = nd.inputs ? Object.keys(nd.inputs).sort().map(k => nd.inputs[k]) : []; + const oldOutputs = nd.outputs ? Object.keys(nd.outputs).sort().map(k => nd.outputs[k]) : []; + + const savedInputs = []; + for (let i = 0; i < programNode.inputPins.length; i++) { + const pin = programNode.inputPins[i]; + if (pin.wire) { + savedInputs[i] = { srcPin: pin.wire.attrs.src, oldType: oldInputs[i]?.dataType }; + deleteWire(pin.wire); + } + } + + const savedOutputs = []; + for (let j = 0; j < programNode.outputPins.length; j++) { + const pin = programNode.outputPins[j]; + const wires = [...(Array.isArray(pin.wire) ? pin.wire : (pin.wire ? [pin.wire] : []))].filter(Boolean); + savedOutputs[j] = wires.map(w => { + const destPin = w.attrs.dest; + const oldType = oldOutputs[j]?.dataType; + deleteWire(w); + return { destPin, oldType }; + }); + } + + const savedExecIn = []; + if (programNode.execInPins && programNode.execInPins[0]) { + const wires = [...(programNode.execInPins[0].wire || [])].filter(Boolean); + for (const w of wires) { + savedExecIn.push({ srcPin: w.attrs.src }); + deleteWire(w); + } + } + + const savedExecOut = []; + if (programNode.execOutPins) { + for (const ep of programNode.execOutPins) { + if (ep.wire) { + savedExecOut.push({ destPin: ep.wire.attrs.dest }); + deleteWire(ep.wire); + } + } + } + + const pos = grp.position(); + grp.destroy(); + + const newNode = Nodes.CreateCallNode(funcName, inputParamsArr, outputParamsArr, pos, layer, stage, docString || ''); + const newCC = newNode.grp.customClass; + + for (let i = 0; i < savedInputs.length && i < inputParamsArr.length; i++) { + const saved = savedInputs[i]; + if (!saved) continue; + const newDestPin = newCC.inputPins[i]?.thisNode; + if (!newDestPin) continue; + addConnectionWire(newDestPin, saved.srcPin, stage, 1, wireLayer); + const newType = inputParamsArr[i]?.dataType; + if (!isTypeCompatible(saved.srcPin.attrs.pinDataType, newType)) { + const wire = newCC.inputPins[i].wire; + if (wire) applyMismatchToWire(wire, wireLayer, stage); + } + } + + const outPins = newNode.grp.find('.pin').filter(p => p.attrs.pinType === 'outp'); + outPins.sort((a, b) => { + const ai = parseInt(String(a.attrs.helper || '').split('-')[1], 10) || 0; + const bi = parseInt(String(b.attrs.helper || '').split('-')[1], 10) || 0; + return ai - bi; + }); + + for (let j = 0; j < savedOutputs.length && j < outputParamsArr.length; j++) { + const newSrcPin = outPins[j]; + if (!newSrcPin) continue; + for (const { destPin, oldType } of savedOutputs[j]) { + addConnectionWire(destPin, newSrcPin, stage, 1, wireLayer); + const newType = outputParamsArr[j]?.dataType; + if (!isTypeCompatible(newType, destPin.attrs.pinDataType)) { + const inpIdx = destPin.attrs.helper ? parseInt(String(destPin.attrs.helper).split('-')[1], 10) : 0; + const wireToMark = destPin.getParent().customClass?.inputPins?.[inpIdx]?.wire; + if (wireToMark) applyMismatchToWire(wireToMark, wireLayer, stage); + } + } + } + + for (const { srcPin } of savedExecIn) { + if (newCC.execInPins && newCC.execInPins[0]) { + const destPin = newCC.execInPins[0].thisNode; + addConnectionWire(destPin, srcPin, stage, 1, wireLayer); + } + } + + for (let k = 0; k < savedExecOut.length && newCC.execOutPins && newCC.execOutPins[k]; k++) { + const { destPin } = savedExecOut[k]; + const srcPin = newCC.execOutPins[k].thisNode; + addConnectionWire(destPin, srcPin, stage, 1, wireLayer); + } + } + } + if (stage) stage.draw(); + }, + + rebuildReturnNode: function (node, outputParams, layer, stage, wireLayer) { + const programNode = node; + const grp = programNode.grp; + const oldNd = programNode.nodeDescription; + const oldInputs = oldNd.inputs ? Object.keys(oldNd.inputs).sort().map(k => oldNd.inputs[k]) : []; + + const savedInputs = []; + for (let i = 0; i < programNode.inputPins.length; i++) { + const pin = programNode.inputPins[i]; + if (pin.wire) { + savedInputs[i] = { srcPin: pin.wire.attrs.src, oldType: oldInputs[i]?.dataType }; + deleteWire(pin.wire); + } + } + + const savedExecIns = []; + for (let i = 0; i < programNode.execInPins.length; i++) { + const ep = programNode.execInPins[i]; + if (ep.wire && ep.wire.length > 0) { + const wireCopy = [...ep.wire]; + savedExecIns[i] = wireCopy.map(w => { + const srcPin = w.attrs.src; + deleteWire(w); + return srcPin; + }); + } + } + + const pos = grp.position(); + const grpId = grp.id(); + grp.destroy(); + + const newNd = this.buildReturnDescription(outputParams); + const newNode = new this.ProgramNode(newNd, { + x: pos.x * stage.scaleX() + stage.x(), + y: pos.y * stage.scaleY() + stage.y() + }, layer, stage); + newNode.grp.id(grpId); + const newCC = newNode.grp.customClass; + + for (let i = 0; i < savedExecIns.length; i++) { + if (!savedExecIns[i]) continue; + const newExecInPin = newCC.execInPins[0]?.thisNode; + if (newExecInPin) { + for (const srcPin of savedExecIns[i]) { + addConnectionWire(newExecInPin, srcPin, stage, 1, wireLayer); + } + } + } + + const outputParamsArr = outputParams || []; + for (let i = 0; i < savedInputs.length && i < outputParamsArr.length; i++) { + const saved = savedInputs[i]; + if (!saved) continue; + const newDestPin = newCC.inputPins[i]?.thisNode; + if (!newDestPin) continue; + addConnectionWire(newDestPin, saved.srcPin, stage, 1, wireLayer); + const newType = outputParamsArr[i]?.dataType; + if (!isTypeCompatible(saved.srcPin.attrs.pinDataType, newType)) { + const wire = newCC.inputPins[i].wire; + if (wire) applyMismatchToWire(wire, wireLayer, stage); + } + } + return newNode; + }, +}; + +/* + +//required json +{ + type: string, + id: num, + inputs:{ + count: integer, + execIn1:{ + name: "", + wire: KonvaWire else null + } + ip1: { + dataType: string, + default: num/str etc, + value: num/str etc, + name: "" + wire: Konva.Line else null if no wire + } + } + outputs:{ + count: integer, + execOut1:{ + name: "", + wire: KonvaWire else null + } + out1: { + dataType: string, + default: num/str etc, + value: num/str etc, + name: "" + wire: Konva.Line else null if no wire + } + } + +} + + +*/ diff --git a/javascript/InputBox/InputBox.js b/src/js/nodes/nodeInputBox.js similarity index 99% rename from javascript/InputBox/InputBox.js rename to src/js/nodes/nodeInputBox.js index f345c14..1ce1663 100644 --- a/javascript/InputBox/InputBox.js +++ b/src/js/nodes/nodeInputBox.js @@ -1,4 +1,4 @@ - + export var InputBox = class{ constructor(stage, layer, type, grp, position, colorMap, inputPin, iplabel, inputPinsPlaced, defValueContainer, defValueContainerForSave) { @@ -103,4 +103,4 @@ export var InputBox = class{ }); grp.add(this.inputBox); } -} \ No newline at end of file +} diff --git a/javascript/setLocationOfNode/setLocationOfNode.js b/src/js/nodes/nodePosition.js similarity index 81% rename from javascript/setLocationOfNode/setLocationOfNode.js rename to src/js/nodes/nodePosition.js index b405820..592b506 100644 --- a/javascript/setLocationOfNode/setLocationOfNode.js +++ b/src/js/nodes/nodePosition.js @@ -1,6 +1,6 @@ -export var setLocationOfNode = { +export var setLocationOfNode = { place: function(node, location , stage){ node.x((location.x - stage.x()) / stage.scaleX()); node.y((location.y - stage.y()) / stage.scaleY()); } -} \ No newline at end of file +} diff --git a/javascript/Wiring/Wiring.js b/src/js/nodes/wiring.js similarity index 72% rename from javascript/Wiring/Wiring.js rename to src/js/nodes/wiring.js index c1924dd..d200002 100644 --- a/javascript/Wiring/Wiring.js +++ b/src/js/nodes/wiring.js @@ -1,5 +1,6 @@ -import { deleteWire, deleteHalfWire } from '../Delete/delete.js' -import { colorMap } from '../ColorMap/colorMap.js' +import { deleteWire, deleteHalfWire } from '../editor/deleteHandler.js' +import { colorMap } from '../core/colorMap.js' +import { tabManager } from '../editor/tabManager.js' let placeLocation = function (location, stage) { return { x: (location.x - stage.x()) / stage.scaleX(), @@ -7,7 +8,7 @@ let placeLocation = function (location, stage) { }; } export var Wiring = { - enableWiring: function (stage, layer) { + enableWiring: function (stage) { let currentPinType = null; let currentPinDataType = null; function isValidMatch(pinType, targetPinDataType) { @@ -22,19 +23,18 @@ export var Wiring = { let isWiring = false; let src = null; let dest = null; - let wireLayer = new Konva.Layer({ - id: 'wireLayer', - }); + let activeWireLayer = null; + let activeNodeLayer = null; let drawWire = null; let potentialTarget = null; let dir = 0; let wireColor = null; let originPreOccupied = null; - stage.add(wireLayer); - wireLayer.zIndex(0); stage.on('mousedown', (e) => { if (e.target.name() == 'pin' && e.evt.button == 0) { + activeWireLayer = tabManager.getActiveWireLayer(); + activeNodeLayer = tabManager.getActiveLayer(); src = e.target; currentPinType = e.target.attrs.pinType; currentPinDataType = e.target.attrs.pinDataType; @@ -61,8 +61,8 @@ export var Wiring = { target: src, }); setWirePoints(destLoc, srcLoc, dir, drawWire); - wireLayer.add(drawWire); - wireLayer.draw(); + activeWireLayer.add(drawWire); + activeWireLayer.draw(); } }); stage.on('mouseup', (e) => { @@ -86,7 +86,7 @@ export var Wiring = { let srcLoc = placeLocation(src.getAbsolutePosition(), stage); let destLoc = placeLocation(stage.getPointerPosition(), stage); setWirePoints(destLoc, srcLoc, dir, drawWire); - wireLayer.draw(); + activeWireLayer.draw(); } }) @@ -97,7 +97,7 @@ export var Wiring = { deleteHalfWire(drawWire, originPreOccupied); if (e.target && src && e.target.name() == 'pin' && src != e.target && src.getParent() !== e.target.getParent() && isValidMatch(e.target.attrs.pinType, e.target.attrs.pinDataType)) { dest = e.target; - addConnectionWire(dest, src, stage, dir, wireLayer); + addConnectionWire(dest, src, stage, dir, activeWireLayer); } src.getParent().draggable(true); src = null; @@ -109,8 +109,8 @@ export var Wiring = { currentPinType = null; currentPinDataType = null; wireColor = null; - wireLayer.draw(); - layer.draw(); + activeWireLayer.draw(); + activeNodeLayer.draw(); } } else { @@ -126,8 +126,8 @@ export var Wiring = { currentPinType = null; currentPinDataType = null; wireColor = null; - wireLayer.draw(); - layer.draw(); + activeWireLayer.draw(); + activeNodeLayer.draw(); } } } @@ -145,13 +145,27 @@ export function addConnectionWire(dest, src, stage, dir, wireLayer) { name: "isConnection", bezier: true, }); + let closeIndicator = null; + let closeHideTimeout = null; connectionWire.on('mouseover', (e) => { + if (closeHideTimeout) { clearTimeout(closeHideTimeout); closeHideTimeout = null; } connectionWire.strokeWidth(5); + if (closeIndicator) { + const pts = connectionWire.points(); + if (pts.length >= 8) { + closeIndicator.x((pts[0] + pts[6]) / 2); + closeIndicator.y((pts[1] + pts[7]) / 2); + } + closeIndicator.visible(true); + } wireLayer.draw(); }); connectionWire.on('mouseleave', (e) => { - connectionWire.strokeWidth(2); - wireLayer.draw(); + closeHideTimeout = setTimeout(() => { + connectionWire.strokeWidth(2); + if (closeIndicator) closeIndicator.visible(false); + wireLayer.draw(); + }, 100); }) let srcLoc = placeLocation(src.getAbsolutePosition(), stage); let destLoc = placeLocation(dest.getAbsolutePosition(), stage); @@ -207,6 +221,76 @@ export function addConnectionWire(dest, src, stage, dir, wireLayer) { } ); wireLayer.draw(); + + const closeBtnSize = 18; + closeIndicator = new Konva.Group({ visible: false }); + const closeBg = new Konva.Rect({ + width: closeBtnSize, + height: closeBtnSize, + offsetX: closeBtnSize / 2, + offsetY: closeBtnSize / 2, + fill: 'rgba(0,0,0,0.7)', + stroke: 'rgba(255,255,255,0.3)', + strokeWidth: 1, + cornerRadius: 4, + }); + const closeText = new Konva.Text({ + text: '\u00D7', + fontSize: 14, + fontFamily: 'Verdana', + fill: '#fff', + width: closeBtnSize, + height: closeBtnSize, + offsetX: closeBtnSize / 2, + offsetY: closeBtnSize / 2, + align: 'center', + verticalAlign: 'middle', + }); + closeIndicator.add(closeBg); + closeIndicator.add(closeText); + const initPts = connectionWire.points(); + if (initPts.length >= 8) { + closeIndicator.x((initPts[0] + initPts[6]) / 2); + closeIndicator.y((initPts[1] + initPts[7]) / 2); + } + wireLayer.add(closeIndicator); + connectionWire._closeIndicator = closeIndicator; + + closeIndicator.on('mouseenter', () => { + if (closeHideTimeout) { clearTimeout(closeHideTimeout); closeHideTimeout = null; } + closeBg.fill('rgba(200,50,50,0.85)'); + document.body.style.cursor = 'pointer'; + wireLayer.draw(); + }); + closeIndicator.on('mouseleave', () => { + closeBg.fill('rgba(0,0,0,0.7)'); + document.body.style.cursor = 'default'; + connectionWire.strokeWidth(2); + closeIndicator.visible(false); + wireLayer.draw(); + }); + closeIndicator.on('click', () => { + document.body.style.cursor = 'default'; + if (closeHideTimeout) { clearTimeout(closeHideTimeout); closeHideTimeout = null; } + deleteWire(connectionWire); + stage.draw(); + }); + + const srcParentGrp = connectionWire.attrs.src.getParent(); + const destParentGrp = connectionWire.attrs.dest.getParent(); + function updateClosePos() { + const p = connectionWire.points(); + if (p.length >= 8) { + closeIndicator.x((p[0] + p[6]) / 2); + closeIndicator.y((p[1] + p[7]) / 2); + } + } + srcParentGrp.on('dragmove.wireclose', updateClosePos); + destParentGrp.on('dragmove.wireclose', updateClosePos); + connectionWire._closeDragCleanup = () => { + srcParentGrp.off('dragmove.wireclose', updateClosePos); + destParentGrp.off('dragmove.wireclose', updateClosePos); + }; } function setWirePoints(destLoc, srcLoc, dir, wire) { diff --git a/src/js/persistence/saveAndLoad.js b/src/js/persistence/saveAndLoad.js new file mode 100644 index 0000000..3be1f41 --- /dev/null +++ b/src/js/persistence/saveAndLoad.js @@ -0,0 +1,268 @@ +import { Nodes } from '../nodes/nodeFactory.js' +import { addConnectionWire } from '../nodes/wiring.js' +import { variableList } from '../ui/variableList.js' +import { showAlert, vscriptOnLoad } from '../ui/dialogs.js' +import { getGroupsData, createNodeGroup } from '../editor/nodeGroup.js' +import { tabManager } from '../editor/tabManager.js' +function writeError(err, msg) { + document.getElementById("console-window").classList.toggle("hidden", false); + let codeDoc = document.getElementById("console").contentWindow.document; + codeDoc.open(); + codeDoc.writeln( + `\n + + + + "${msg}"
      + ${err} +
      + + + ` + ); + codeDoc.close(); +} +let placeLocation = function (location) { + //"this" is stage + return { + x: (location.x - this.x()) / this.scaleX(), + y: (location.y - this.y()) / this.scaleY() + }; +} +function serializeLayerData(layer, wireLayer) { + const nodesData = []; + const wireData = []; + layer.find('.aProgramNodeGroup').forEach((node) => { + if (node.name() === 'aProgramNodeGroup') { + nodesData.push({ + position: node.position(), + nodeDescription: node.customClass.nodeDescription, + }); + } + }); + wireLayer.find('.isConnection').forEach((aWire) => { + if (aWire.name() === 'isConnection') { + wireData.push({ + srcId: aWire.attrs.src.id(), + destId: aWire.attrs.dest.id(), + }); + } + }); + return { nodesData, wireData }; +} + +function buildFullExportData(layer, wireLayer) { + const { nodesData, wireData } = serializeLayerData(layer, wireLayer); + const mainTab = tabManager.getTab('main'); + const globalVars = mainTab ? mainTab.variables : variableList.variables; + const funcTabs = tabManager.getAllFunctionTabs(); + const functions = funcTabs.map(ft => { + const ftData = serializeLayerData(ft.layer, ft.wireLayer); + return { + name: ft.name, + inputParams: ft.inputParams.map(p => ({ name: p.name, dataType: p.dataType, defValue: p.defValue })), + outputParams: ft.outputParams.map(p => ({ name: p.name, dataType: p.dataType })), + variables: ft.variables.map(v => ({ name: v.name, dataType: v.dataType, value: v.value })), + docString: ft.docString || '', + nodesData: ftData.nodesData, + wireData: ftData.wireData, + groupsData: getGroupsData(ft.layer), + }; + }); + return { + variables: globalVars, + nodesData, + wireData, + groupsData: getGroupsData(layer), + functions, + }; +} + +export class Export { + constructor(stage, layer, wireLayer) { + document.getElementById('export').addEventListener("click", (e) => { + const exportScript = buildFullExportData(layer, wireLayer); + let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportScript)); + let exportAnchorElem = document.getElementById('exportAnchorElem'); + exportAnchorElem.setAttribute("href", dataStr); + const ts = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-'); + exportAnchorElem.setAttribute("download", `wireScript_${ts}.json`); + exportAnchorElem.click(); + }); + } +} +export function refresh(layer, wireLayer) { + const funcTabs = tabManager.getAllFunctionTabs(); + for (const ft of [...funcTabs]) { + tabManager.closeTab(ft.id); + } + if (tabManager.getActiveTabId() !== 'main') { + tabManager.switchTab('main'); + } + layer.destroyChildren(); + wireLayer.destroyChildren(); + variableList.deleteAllVariables(); + const mainTab = tabManager.getTab('main'); + if (mainTab) mainTab.variables = variableList.variables; + layer.draw(); + wireLayer.draw(); +} + +export class Import { + constructor(stage, layer, wireLayer, script) { + refresh(layer, wireLayer); + let json = null; + try { + json = JSON.parse(script); + } + catch (err) { + writeError(err, "Error In Loading JSON(JSON TEMPERED)"); + } + // console.log(json); + printContent(json, stage, layer, wireLayer); + } +} +export class Save { + constructor(stage, layer, wireLayer) { + document.getElementById('save').addEventListener("click", (e) => { + const exportScript = buildFullExportData(layer, wireLayer); + localStorage.setItem('lastLoadWireScriptJSON', JSON.stringify(exportScript)); + let savingWindow = document.getElementById("saving"); + // let importMenu = document.getElementById("import-menu"); + [...document.getElementsByClassName("sidebox")].forEach(value => { + if (value !== savingWindow) { + value.classList.toggle("hidden", true); + } + else { + value.classList.toggle("hidden", false); + } + }) + setTimeout(() => { + savingWindow.classList.toggle("hidden", true); + }, 600); + + }); + window.addEventListener("load", () => { + // console.log("loaded"); + prompLastSave(stage, layer, wireLayer); + }) + } +} + +export function prompLastSave(stage, layer, wireLayer) { + let saveMenu = document.getElementById("save-menu"); + [...document.getElementsByClassName("sidebox")].forEach(value => { + value.classList.toggle("hidden", true); + }); + // document.getElementById("saving").classList.toggle("hidden", true); + // document.getElementById("import-menu").classList.toggle("hidden", true); + if (localStorage.getItem('lastLoadWireScriptJSON') && localStorage.getItem('lastLoadWireScriptJSON') != "{\"variables\":[],\"nodesData\":[],\"wireData\":[]}") { + saveMenu.classList.toggle("hidden", false); + document.getElementById("load-btn").onclick = function () { + new Import(stage, layer, wireLayer, localStorage.getItem('lastLoadWireScriptJSON')); + saveMenu.classList.toggle("hidden", true); + }; + document.getElementById("load-cancel-btn").onclick = function () { + saveMenu.classList.toggle("hidden", true); + }; + } + else{ + vscriptOnLoad(stage); + showAlert('No Previous Save Was Found'); + } +} + +function loadLayerContent(nodesData, wireData, targetLayer, wireLayer, stage) { + for (let aNode of nodesData) { + try { + new Nodes.ProgramNode(aNode.nodeDescription, + { x: aNode.position.x * stage.scaleX() + stage.x(), y: aNode.position.y * stage.scaleY() + stage.y() }, + targetLayer, stage); + } catch (err) { + writeError(err, "Error Occurred In Importing The JSON(Node Description Not Valid)"); + } + } + for (let aWire of wireData) { + let src = targetLayer.findOne(`#${aWire.srcId}`); + let dest = targetLayer.findOne(`#${aWire.destId}`); + try { + addConnectionWire(dest, src, stage, 1, wireLayer); + } catch (err) { + writeError(err, "Error Occurred In Importing The JSON(Wire Data Not Valid)"); + } + } +} + +function printContent(json, stage, layer, wireLayer) { + loadLayerContent(json.nodesData, json.wireData, layer, wireLayer, stage); + + const mainTab = tabManager.getTab('main'); + for (let aVariable of json.variables) { + try { + variableList.addVariable(aVariable); + } catch (err) { + writeError(err, "Error Occurred In Importing The JSON(Variable Data Not Valid)"); + } + } + if (mainTab) mainTab.variables = variableList.variables; + if (json.groupsData) { + for (let g of json.groupsData) { + try { + createNodeGroup(g.position, g.width, g.height, g.name, layer, stage); + } catch (err) { + writeError(err, "Error Occurred In Importing The JSON(Group Data Not Valid)"); + } + } + } + + if (json.functions && json.functions.length > 0) { + for (const funcData of json.functions) { + try { + tabManager._suppressAutoNodes = true; + const tab = tabManager.createTab(funcData.name); + tabManager._suppressAutoNodes = false; + if (!tab) continue; + + tab.inputParams = funcData.inputParams || []; + tab.outputParams = funcData.outputParams || []; + tab.variables = funcData.variables || []; + tab.docString = funcData.docString || ''; + tab.saved = { + name: tab.name, + inputParams: tab.inputParams.map(p => ({ ...p })), + outputParams: tab.outputParams.map(p => ({ ...p })), + docString: tab.docString, + }; + + loadLayerContent(funcData.nodesData, funcData.wireData, tab.layer, tab.wireLayer, stage); + + const beginNode = tab.layer.findOne('#FunctionBegin'); + if (beginNode) tab.beginNodeId = beginNode.id(); + const returnNode = tab.layer.findOne('#Return'); + if (returnNode) tab.returnNodeId = returnNode.id(); + + if (funcData.groupsData) { + for (let g of funcData.groupsData) { + createNodeGroup(g.position, g.width, g.height, g.name, tab.layer, stage); + } + } + + tab.layer.draw(); + tab.wireLayer.draw(); + tabManager._emit('functionSaved', tab); + } catch (err) { + tabManager._suppressAutoNodes = false; + writeError(err, "Error Occurred In Importing The JSON(Function Data Not Valid)"); + } + } + tabManager.switchTab('main'); + } + + layer.draw(); + wireLayer.draw(); +} diff --git a/src/js/registry/index.js b/src/js/registry/index.js new file mode 100644 index 0000000..2f8083e --- /dev/null +++ b/src/js/registry/index.js @@ -0,0 +1,16 @@ +/** + * Node registry entry point. Import this once (e.g. from main.js) to register all built-in nodes. + * Then use the registry API for node descriptions, context menu order, and compiler codegen. + */ +import './nodeDefinitions.js'; +export { + registerNode, + registerMenuOrder, + buildNodeDescription, + getDefinition, + getMenuOrder, + getMenuOrderGroupedByCategory, + runExecCodegen, + runExprCodegen, + hasType, +} from './registry.js'; diff --git a/src/js/registry/nodeDefinitions.js b/src/js/registry/nodeDefinitions.js new file mode 100644 index 0000000..6941078 --- /dev/null +++ b/src/js/registry/nodeDefinitions.js @@ -0,0 +1,2686 @@ +/** + * All built-in node definitions in one place. + * + * To add a new node (one place only): + * 1. Call registerNode({ id, label?, schema, execCodegen?, exprCodegen? }). + * 2. schema: { execIn?, execOut?: [{ execOutTitle?, outOrder? }], inputs?, outputs?, color, rows, colums }. + * 3. execCodegen(compiler, node): use compiler.script, compiler.getExecOut(node), compiler.getInputPins(node), compiler.coreAlgorithm(), compiler.handleInputs(). + * 4. exprCodegen(compiler, inputNode): return a string expression; use compiler.getInputPins(inputNode.node), inputNode.srcOutputPinNumber for multi-output nodes. + * 5. Add the node id to the registerMenuOrder() array at the bottom (use null for a separator). + */ +import { registerNode, registerMenuOrder } from './registry.js'; +import { BuilInFunctions } from '../compiler/builtInFunctions.js'; + +// ---------- Helper used by codegen (compiler has getExecOut, getInputPins, handleInputs, script, coreAlgorithm, builtin_functions) ---------- +// Codegen functions receive (compiler, node) or (compiler, inputNode) and use compiler.* freely. + +// ---------- Flow: Begin, Print, Alert, Confirm, Prompt ---------- +registerNode({ + id: 'Begin', + schema: { + execIn: false, + execOut: [{ execOutTitle: null, outOrder: 0 }], + color: 'Begin', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const execOutPins = compiler.getExecOut(node); + compiler.coreAlgorithm(execOutPins[0]); // traverse first so script has generated code + let func_string = `/////////CodeWire Functions Space Begins/////////////\n\n`; + for (const each_function in compiler.builtin_functions) { + func_string = func_string + BuilInFunctions[each_function]; + } + func_string += `\n/////////CodeWire Functions Space Ends/////////////\n//\n//\n/////////Generated JS Code Space Begins/////////////\n`; + compiler.script = func_string + compiler.script; + compiler.script += `\n/////////Generated JS Code Space Ends/////////////`; + }, +}); + +registerNode({ + id: 'Print', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Value', dataType: 'Data', defValue: "'hello'" }], + color: 'Print', + rows: 3, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `console.log(${compiler.handleInputs(inputPins[0])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, +}); + +registerNode({ + id: 'Alert', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Value', dataType: 'Data', defValue: "'hello'" }], + color: 'Print', + rows: 3, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `alert(${compiler.handleInputs(inputPins[0])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, +}); + +registerNode({ + id: 'Confirm', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Message', dataType: 'String', defValue: "'Ok'" }], + outputs: [{ outputTitle: 'Ok?', dataType: 'Boolean', outOrder: 1 }], + color: 'Print', + rows: 3, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.builtin_functions = { ...compiler.builtin_functions, _confirm: true }; + compiler.script += `let _confirm_answer${node._id} = _confirm(${compiler.handleInputs(inputPins[0])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + return `_confirm_answer${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'Prompt', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Message', dataType: 'String', defValue: "'Ok'" }, + { inputTitle: 'Default', dataType: 'String', defValue: "'Yes'" }, + ], + outputs: [ + { outputTitle: 'Ok?', dataType: 'Boolean', outOrder: 1 }, + { outputTitle: 'Value', dataType: 'String', outOrder: 2 }, + ], + color: 'Print', + rows: 3, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.builtin_functions = { ...compiler.builtin_functions, _prompt: true }; + compiler.script += `let [_prompt_ok${node._id}, _prompt_value${node._id}] = _prompt(${compiler.handleInputs(inputPins[0])}, ${compiler.handleInputs(inputPins[1])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) return `_prompt_ok${inputNode.node._id}`; + return `_prompt_value${inputNode.node._id}`; + }, +}); + +// ---------- Flow: If/Else, While, For, ForEach, Break, Continue ---------- +registerNode({ + id: 'If/Else', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'True', outOrder: 0 }, + { execOutTitle: 'False', outOrder: 1 }, + { execOutTitle: 'Done', outOrder: 2 }, + ], + inputs: [{ inputTitle: 'Bool', dataType: 'Boolean', defValue: true }], + color: 'Logic', + rows: 3, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `if(${compiler.handleInputs(inputPins[0])}){\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `}\nelse{\n`; + compiler.coreAlgorithm(execOutPins[1]); + compiler.script += `}\n`; + compiler.coreAlgorithm(execOutPins[2]); + }, +}); + +registerNode({ + id: 'WhileLoop', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop', outOrder: 0 }, + { execOutTitle: 'Done', outOrder: 1 }, + ], + inputs: [{ inputTitle: 'Condition', dataType: 'Boolean', defValue: true }], + color: 'Logic', + rows: 3, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += ` while(${compiler.handleInputs(inputPins[0])}){\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `}\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, +}); + +registerNode({ + id: 'ForLoop', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 2 }, + ], + inputs: [ + { inputTitle: 'From', dataType: 'Number', defValue: 0 }, + { inputTitle: 'To(Excl)', dataType: 'Number', defValue: 10 }, + { inputTitle: 'Increment', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Index', dataType: 'Number', outOrder: 1 }], + color: 'Logic', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const forVar = `i${node._id}`; + compiler.script += `for(let ${forVar} = (${compiler.handleInputs(inputPins[0])}); ${forVar} < (${compiler.handleInputs(inputPins[1])}); ${forVar} += (${compiler.handleInputs(inputPins[2])})){\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `}\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + return `i${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'ForEachLoop', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 4 }, + ], + inputs: [{ inputTitle: 'Array', dataType: 'Array', defValue: '[]', isInputBoxRequired: false }], + outputs: [ + { outputTitle: 'Value', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 2 }, + ], + color: 'Logic', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const forVar = `i${node._id}`; + compiler.script += `${compiler.handleInputs(inputPins[0])}.forEach((value${forVar}, ${forVar}, array${forVar}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) return `valuei${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 1) return `i${inputNode.node._id}`; + return `arrayi${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'Continue', + schema: { + execIn: true, + execOut: [], + color: 'Logic', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + compiler.script += `continue;\n`; + }, +}); + +registerNode({ + id: 'Break', + schema: { + execIn: true, + execOut: [], + color: 'Logic', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + compiler.script += `break;\n`; + }, +}); + +registerNode({ + id: 'Assert', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Condition', dataType: 'Boolean', defValue: true }], + color: 'Logic', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `if (!(${compiler.handleInputs(inputPins[0])})) throw new Error('Assertion failed');\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, +}); + +registerNode({ + id: 'Sleep', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Ms', dataType: 'Number', defValue: 1000 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.builtin_functions = { ...compiler.builtin_functions, _sleep: true }; + compiler.script += `_sleep(${compiler.handleInputs(inputPins[0])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, +}); + +registerNode({ + id: 'Try/Catch', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Try', outOrder: 0 }, + { execOutTitle: 'Catch', outOrder: 1 }, + { execOutTitle: 'Continue', outOrder: 2 }, + ], + color: 'Logic', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const execOutPins = compiler.getExecOut(node); + compiler.script += `try {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `} catch(_err) {\n`; + compiler.coreAlgorithm(execOutPins[1]); + compiler.script += `}\n`; + compiler.coreAlgorithm(execOutPins[2]); + }, +}); + +// ---------- Math ---------- +registerNode({ + id: 'Add', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} + ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Subtract', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} - ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Multiply', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} * ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Divide', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} / ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Modulo', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 1 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 2 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} % ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Power', + schema: { + inputs: [ + { inputTitle: 'Base', dataType: 'Number', defValue: 0 }, + { inputTitle: 'Exp', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.pow(${compiler.handleInputs(inputPins[0])}, ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Random', + schema: { + outputs: [{ outputTitle: 'Random[0,1)', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler) { + return `Math.random()`; + }, +}); + +registerNode({ + id: 'Ceil', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Number', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.ceil(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'Floor', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Number', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.floor(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'Max(Num)', + schema: { + inputs: [ + { inputTitle: 'A', dataType: 'Number', defValue: 0 }, + { inputTitle: 'B', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.max(${compiler.handleInputs(inputPins[0])}, ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Min(Num)', + schema: { + inputs: [ + { inputTitle: 'A', dataType: 'Number', defValue: 0 }, + { inputTitle: 'B', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.min(${compiler.handleInputs(inputPins[0])}, ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Round', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Number', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.round(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'Abs', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Number', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.abs(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'Clamp(Num)', + schema: { + inputs: [ + { inputTitle: 'Value', dataType: 'Number', defValue: 0 }, + { inputTitle: 'Min', dataType: 'Number', defValue: 0 }, + { inputTitle: 'Max', dataType: 'Number', defValue: 10 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + const v = compiler.handleInputs(inputPins[0]); + const min = compiler.handleInputs(inputPins[1]); + const max = compiler.handleInputs(inputPins[2]); + return `Math.min(Math.max(${v}, ${min}), ${max})`; + }, +}); + +// ---------- Comparison: Swap, Equals, Not Equals, Less, LessEq, Greater, GreaterEq ---------- +registerNode({ + id: 'Swap', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Ref1', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Ref2', dataType: 'Data', isInputBoxRequired: false }, + ], + outputs: [ + { outputTitle: 'Ref1', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Ref2', dataType: 'Data', outOrder: 2 }, + ], + color: 'Func', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `[${compiler.handleInputs(inputPins[0])}, ${compiler.handleInputs(inputPins[1])}] = [${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[0])}];\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[inputNode.srcOutputPinNumber])}`; + }, +}); + +registerNode({ + id: 'Equals', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Data', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Data', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} === ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Not Equals', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Data', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Data', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} !== ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Less', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} < ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'LessEq', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} <= ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Greater', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} > ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'GreaterEq', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 0 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} >= ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +// ---------- Logic: AND, OR, XOR, NEG, bAND, bOR, bXOR, bNEG ---------- +registerNode({ + id: 'AND', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Boolean', defValue: true }, + { inputTitle: 'ValueB', dataType: 'Boolean', defValue: true }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} && ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'OR', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Boolean', defValue: true }, + { inputTitle: 'ValueB', dataType: 'Boolean', defValue: true }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} || ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'XOR', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Boolean', defValue: true }, + { inputTitle: 'ValueB', dataType: 'Boolean', defValue: true }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} ^ ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'NEG', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Boolean', defValue: false }], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `!(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'Ternary', + schema: { + inputs: [ + { inputTitle: 'Condition', dataType: 'Boolean', defValue: true }, + { inputTitle: 'Then', dataType: 'Data', defValue: 0 }, + { inputTitle: 'Else', dataType: 'Data', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Data', outOrder: 0 }], + color: 'Logic', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} ? ${compiler.handleInputs(inputPins[1])} : ${compiler.handleInputs(inputPins[2])})`; + }, +}); + +registerNode({ + id: 'bAND', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 1 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} & ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'bOR', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 1 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} | ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'bXOR', + schema: { + inputs: [ + { inputTitle: 'ValueA', dataType: 'Number', defValue: 1 }, + { inputTitle: 'ValueB', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} ^ ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'bNEG', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Number', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'Number', outOrder: 0 }], + color: 'Math', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `~${compiler.handleInputs(inputPins[0])}`; + }, +}); + +// ---------- Func: OpenWindow, HttpRequest, GetByName(JSON) ---------- +registerNode({ + id: 'OpenWindow', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'URL', dataType: 'String', defValue: "'link'" }], + outputs: [{ outputTitle: 'Success?', dataType: 'Boolean', outOrder: 1 }], + color: 'Func', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.builtin_functions = { ...compiler.builtin_functions, _newWindow: true }; + compiler.script += `let _window_opened${node._id} = _newWindow(${compiler.handleInputs(inputPins[0])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + return `_window_opened${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'HttpRequest', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'OnSuccess', outOrder: 0 }, + { execOutTitle: 'OnFail', outOrder: 2 }, + { execOutTitle: 'Continue', outOrder: 3 }, + ], + inputs: [{ inputTitle: 'URL', dataType: 'String', defValue: "'link'" }], + outputs: [{ outputTitle: 'JSON', dataType: 'Data', outOrder: 1 }], + color: 'Func', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.builtin_functions = { ...compiler.builtin_functions, fetch_data: true }; + compiler.script += `fetch_data(${compiler.handleInputs(inputPins[0])})\n.then((json_data${node._id}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `})\n.catch((err) => {\n`; + compiler.coreAlgorithm(execOutPins[1]); + compiler.script += `});\n`; + compiler.coreAlgorithm(execOutPins[2]); + }, + exprCodegen(compiler, inputNode) { + return `json_data${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'GetByName(JSON)', + schema: { + inputs: [ + { inputTitle: 'JSON', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Name', dataType: 'String', defValue: "'id'" }, + ], + outputs: [{ outputTitle: 'Data', dataType: 'Data', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}[${compiler.handleInputs(inputPins[1])}]`; + }, +}); + +registerNode({ + id: 'ParseInt', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'0'", isInputBoxRequired: false }, + { inputTitle: 'Radix', dataType: 'Number', defValue: 10 }, + ], + outputs: [{ outputTitle: 'Number', dataType: 'Number', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `parseInt(${compiler.handleInputs(inputPins[0])}, ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +// ---------- Str/Array: StrToArray, ArrayToStr ---------- +registerNode({ + id: 'StrToArray', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'String', dataType: 'String', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Str', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `let strArray${node._id} = ${compiler.handleInputs(inputPins[0])}.split('');\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + return `strArray${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'ArrayToStr', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'String', dataType: 'String', outOrder: 1 }], + color: 'Str', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `let arrayStr${node._id} = ${compiler.handleInputs(inputPins[0])}.join('');\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + return `arrayStr${inputNode.node._id}`; + }, +}); + +// ---------- Array: Length, Front, Back, GetByPos, SetByPos, Search, BinarySearch(Num), PushBack, PopBack, PushFront, PopFront, Insert, Sort(Num), isEmpty, Reverse, Max(Array), Min(Array) ---------- +registerNode({ + id: 'Length', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Value', dataType: 'Number', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.length`; + }, +}); + +registerNode({ + id: 'Front', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Front(Ref)', dataType: 'Data', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 11, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}[0]`; + }, +}); + +registerNode({ + id: 'Back', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Back(Ref)', dataType: 'Data', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 11, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}[${compiler.handleInputs(inputPins[0])}.length - 1]`; + }, +}); + +registerNode({ + id: 'GetByPos', + schema: { + inputs: [ + { inputTitle: 'Pos', dataType: 'Number', defValue: 0 }, + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [{ outputTitle: 'Value(Ref)', dataType: 'Data', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[1])}[${compiler.handleInputs(inputPins[0])}]`; + }, +}); + +registerNode({ + id: 'SetByPos', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Pos', dataType: 'Number', defValue: 0 }, + { inputTitle: 'Value', dataType: 'Data', defValue: 1 }, + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [{ outputTitle: 'Value(Ref)', dataType: 'Data', outOrder: 1 }], + color: 'Get', + rows: 4, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[2])}[${compiler.handleInputs(inputPins[0])}] = ${compiler.handleInputs(inputPins[1])};\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[2])}[${compiler.handleInputs(inputPins[0])}]`; + }, +}); + +registerNode({ + id: 'Search', + schema: { + inputs: [ + { inputTitle: 'Value', dataType: 'Data', defValue: 0 }, + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [ + { outputTitle: 'Exist', dataType: 'Boolean', outOrder: 0 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 1 }, + ], + color: 'Get', + rows: 2, + colums: 11, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) { + return `(${compiler.handleInputs(inputPins[1])}.find((value) => value === ${compiler.handleInputs(inputPins[0])}) === ${compiler.handleInputs(inputPins[0])})`; + } + return `(${compiler.handleInputs(inputPins[1])}.findIndex((value) => value === ${compiler.handleInputs(inputPins[0])}))`; + }, +}); + +registerNode({ + id: 'BinarySearch(Num)', + schema: { + inputs: [ + { inputTitle: 'Value', dataType: 'Number', defValue: 0 }, + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [ + { outputTitle: 'Exist', dataType: 'Boolean', outOrder: 0 }, + { outputTitle: 'Lower Bound', dataType: 'Number', outOrder: 1 }, + { outputTitle: 'Upper Bound', dataType: 'Number', outOrder: 2 }, + ], + color: 'Get', + rows: 2, + colums: 13, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) { + compiler.builtin_functions = { ...compiler.builtin_functions, binary_search_exist: true }; + return `binary_search_exist(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[0])})`; + } + if (inputNode.srcOutputPinNumber == 1) { + compiler.builtin_functions = { ...compiler.builtin_functions, lower_bound: true }; + return `lower_bound(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[0])})`; + } + compiler.builtin_functions = { ...compiler.builtin_functions, upper_bound: true }; + return `upper_bound(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'PushBack', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Value', dataType: 'Data', defValue: 1 }, + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Get', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[1])}.push(${compiler.handleInputs(inputPins[0])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[1])}`; + }, +}); + +registerNode({ + id: 'PushFront', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Value', dataType: 'Data', defValue: 1 }, + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Get', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[1])}.unshift(${compiler.handleInputs(inputPins[0])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[1])}`; + }, +}); + +registerNode({ + id: 'PopBack', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Get', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[0])}.pop();\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'PopFront', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Get', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[0])}.shift();\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'Insert', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Pos', dataType: 'Number', defValue: 0 }, + { inputTitle: 'Value', dataType: 'Data', defValue: 1 }, + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Get', + rows: 4, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[2])}.splice(${compiler.handleInputs(inputPins[0])}, 0, ${compiler.handleInputs(inputPins[1])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[2])}`; + }, +}); + +registerNode({ + id: 'Sort(Num)', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Increasing', dataType: 'Boolean', defValue: true }, + ], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Get', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `(${compiler.handleInputs(inputPins[1])}) ? ${compiler.handleInputs(inputPins[0])}.sort((a, b) => a-b) : ${compiler.handleInputs(inputPins[0])}.sort((a, b) => b-a);\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'isEmpty', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])}.length == (0))`; + }, +}); + +registerNode({ + id: 'Reverse', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Array', dataType: 'Array', outOrder: 1 }], + color: 'Get', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[0])}.reverse();\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'Max(Array)', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'MaxValue', dataType: 'Number', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 11, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.max(...${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'Min(Array)', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'MinValue', dataType: 'Number', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 11, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Math.min(...${compiler.handleInputs(inputPins[0])})`; + }, +}); + +// ---------- String: Concat, StringLength, Substring, CharAt, IndexOf(Str), LastIndexOf(Str), Replace, ReplaceAll, Split, Join, Trim, ToUpperCase, ToLowerCase, StartsWith, EndsWith, Includes(Str), Repeat, ParseFloat, ToString ---------- +registerNode({ + id: 'Concat', + schema: { + inputs: [ + { inputTitle: 'A', dataType: 'String', defValue: "''" }, + { inputTitle: 'B', dataType: 'String', defValue: "''" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} + ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'StringLength', + schema: { + inputs: [{ inputTitle: 'String', dataType: 'String', defValue: "''" }], + outputs: [{ outputTitle: 'Length', dataType: 'Number', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 13, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.length`; + }, +}); + +registerNode({ + id: 'Substring', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Start', dataType: 'Number', defValue: 0 }, + { inputTitle: 'End', dataType: 'Number', defValue: 5 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.substring(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[2])})`; + }, +}); + +registerNode({ + id: 'CharAt', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Index', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Char', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.charAt(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'IndexOf(Str)', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Search', dataType: 'String', defValue: "'l'" }, + ], + outputs: [ + { outputTitle: 'Index', dataType: 'Number', outOrder: 0 }, + { outputTitle: 'Found', dataType: 'Boolean', outOrder: 1 }, + ], + color: 'Str', + rows: 2, + colums: 13, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + const str = compiler.handleInputs(inputPins[0]); + const search = compiler.handleInputs(inputPins[1]); + if (inputNode.srcOutputPinNumber == 0) return `${str}.indexOf(${search})`; + return `(${str}.indexOf(${search}) !== -1)`; + }, +}); + +registerNode({ + id: 'LastIndexOf(Str)', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Search', dataType: 'String', defValue: "'l'" }, + ], + outputs: [ + { outputTitle: 'Index', dataType: 'Number', outOrder: 0 }, + { outputTitle: 'Found', dataType: 'Boolean', outOrder: 1 }, + ], + color: 'Str', + rows: 2, + colums: 16, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + const str = compiler.handleInputs(inputPins[0]); + const search = compiler.handleInputs(inputPins[1]); + if (inputNode.srcOutputPinNumber == 0) return `${str}.lastIndexOf(${search})`; + return `(${str}.lastIndexOf(${search}) !== -1)`; + }, +}); + +registerNode({ + id: 'Replace', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Search', dataType: 'String', defValue: "'l'" }, + { inputTitle: 'Replace', dataType: 'String', defValue: "'r'" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.replace(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[2])})`; + }, +}); + +registerNode({ + id: 'ReplaceAll', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Search', dataType: 'String', defValue: "'l'" }, + { inputTitle: 'Replace', dataType: 'String', defValue: "'r'" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.replaceAll(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[2])})`; + }, +}); + +registerNode({ + id: 'Split', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'a,b,c'" }, + { inputTitle: 'Delimiter', dataType: 'String', defValue: "','" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Array', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.split(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Join', + schema: { + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Delimiter', dataType: 'String', defValue: "','" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.join(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Trim', + schema: { + inputs: [{ inputTitle: 'String', dataType: 'String', defValue: "' hello '" }], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.trim()`; + }, +}); + +registerNode({ + id: 'ToUpperCase', + schema: { + inputs: [{ inputTitle: 'String', dataType: 'String', defValue: "'hello'" }], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.toUpperCase()`; + }, +}); + +registerNode({ + id: 'ToLowerCase', + schema: { + inputs: [{ inputTitle: 'String', dataType: 'String', defValue: "'HELLO'" }], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.toLowerCase()`; + }, +}); + +registerNode({ + id: 'StartsWith', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Search', dataType: 'String', defValue: "'he'" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.startsWith(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'EndsWith', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Search', dataType: 'String', defValue: "'lo'" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.endsWith(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Includes(Str)', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'hello'" }, + { inputTitle: 'Search', dataType: 'String', defValue: "'ell'" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 14, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.includes(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Repeat', + schema: { + inputs: [ + { inputTitle: 'String', dataType: 'String', defValue: "'ab'" }, + { inputTitle: 'Count', dataType: 'Number', defValue: 3 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.repeat(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'ParseFloat', + schema: { + inputs: [{ inputTitle: 'String', dataType: 'String', defValue: "'0.0'", isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Number', dataType: 'Number', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 11, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `parseFloat(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'ToString', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Data', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'String', outOrder: 0 }], + color: 'Str', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `String(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +// ---------- Object/JSON: JSON Parse, JSON Stringify, CreateObject, SetProperty, GetProperty, DeleteProperty, HasProperty, ObjectKeys, ObjectValues, ObjectEntries, FromEntries, MergeObjects, ObjectSize, TypeOf, IsNull, IsArray ---------- +registerNode({ + id: 'JSON Parse', + schema: { + inputs: [{ inputTitle: 'String', dataType: 'String', defValue: "'{}'" }], + outputs: [{ outputTitle: 'Object', dataType: 'Data', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `JSON.parse(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'JSON Stringify', + schema: { + inputs: [ + { inputTitle: 'Value', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Indent', dataType: 'Number', defValue: 0 }, + ], + outputs: [{ outputTitle: 'String', dataType: 'String', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 14, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `JSON.stringify(${compiler.handleInputs(inputPins[0])}, null, ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'CreateObject', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + outputs: [{ outputTitle: 'Object', dataType: 'Data', outOrder: 1 }], + color: 'Obj', + rows: 2, + colums: 13, + }, + execCodegen(compiler, node) { + const execOutPins = compiler.getExecOut(node); + compiler.script += `let _obj${node._id} = {};\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + return `_obj${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'SetProperty', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'String', defValue: "'key'" }, + { inputTitle: 'Value', dataType: 'Data', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Object', dataType: 'Data', outOrder: 1 }], + color: 'Obj', + rows: 4, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[0])}[${compiler.handleInputs(inputPins[1])}] = ${compiler.handleInputs(inputPins[2])};\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'GetProperty', + schema: { + inputs: [ + { inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'String', defValue: "'key'" }, + ], + outputs: [ + { outputTitle: 'Value', dataType: 'Data', outOrder: 0 }, + { outputTitle: 'Exists', dataType: 'Boolean', outOrder: 1 }, + ], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) { + return `${compiler.handleInputs(inputPins[0])}[${compiler.handleInputs(inputPins[1])}]`; + } + return `(${compiler.handleInputs(inputPins[1])} in Object(${compiler.handleInputs(inputPins[0])}))`; + }, +}); + +registerNode({ + id: 'DeleteProperty', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'String', defValue: "'key'" }, + ], + outputs: [{ outputTitle: 'Object', dataType: 'Data', outOrder: 1 }], + color: 'Obj', + rows: 2, + colums: 14, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `delete ${compiler.handleInputs(inputPins[0])}[${compiler.handleInputs(inputPins[1])}];\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'HasProperty', + schema: { + inputs: [ + { inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'String', defValue: "'key'" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[1])} in Object(${compiler.handleInputs(inputPins[0])}))`; + }, +}); + +registerNode({ + id: 'ObjectKeys', + schema: { + inputs: [{ inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Keys', dataType: 'Array', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Object.keys(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'ObjectValues', + schema: { + inputs: [{ inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Values', dataType: 'Array', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 13, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Object.values(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'ObjectEntries', + schema: { + inputs: [{ inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Entries', dataType: 'Array', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 14, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Object.entries(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'FromEntries', + schema: { + inputs: [{ inputTitle: 'Entries', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Object', dataType: 'Data', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Object.fromEntries(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'MergeObjects', + schema: { + inputs: [ + { inputTitle: 'ObjectA', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'ObjectB', dataType: 'Data', isInputBoxRequired: false }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Data', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 13, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Object.assign({}, ${compiler.handleInputs(inputPins[0])}, ${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'ObjectSize', + schema: { + inputs: [{ inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Size', dataType: 'Number', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Object.keys(${compiler.handleInputs(inputPins[0])}).length`; + }, +}); + +registerNode({ + id: 'TypeOf', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Data', defValue: 0 }], + outputs: [{ outputTitle: 'Type', dataType: 'String', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `typeof ${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'IsNull', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Data', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `(${compiler.handleInputs(inputPins[0])} == null)`; + }, +}); + +registerNode({ + id: 'IsArray', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Data', defValue: 0 }], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Array.isArray(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +// ---------- Map: CreateMap, MapSet, MapGet, MapHas, MapDelete, MapSize, MapClear, MapKeys, MapValues, MapEntries, MapFromObject, MapToObject, ForEachMap ---------- +registerNode({ + id: 'CreateMap', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + outputs: [{ outputTitle: 'Map', dataType: 'Data', outOrder: 1 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const execOutPins = compiler.getExecOut(node); + compiler.script += `let _map${node._id} = new Map();\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + return `_map${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'MapSet', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'Data', defValue: "'key'" }, + { inputTitle: 'Value', dataType: 'Data', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Map', dataType: 'Data', outOrder: 1 }], + color: 'Obj', + rows: 4, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[0])}.set(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[2])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'MapGet', + schema: { + inputs: [ + { inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'Data', defValue: "'key'" }, + ], + outputs: [ + { outputTitle: 'Value', dataType: 'Data', outOrder: 0 }, + { outputTitle: 'Exists', dataType: 'Boolean', outOrder: 1 }, + ], + color: 'Obj', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) { + return `${compiler.handleInputs(inputPins[0])}.get(${compiler.handleInputs(inputPins[1])})`; + } + return `${compiler.handleInputs(inputPins[0])}.has(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'MapHas', + schema: { + inputs: [ + { inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'Data', defValue: "'key'" }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.has(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'MapDelete', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }, + { inputTitle: 'Key', dataType: 'Data', defValue: "'key'" }, + ], + outputs: [ + { outputTitle: 'Map', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Deleted', dataType: 'Boolean', outOrder: 2 }, + ], + color: 'Obj', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `let _mapDel${node._id} = ${compiler.handleInputs(inputPins[0])}.delete(${compiler.handleInputs(inputPins[1])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) return `${compiler.handleInputs(inputPins[0])}`; + return `_mapDel${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'MapSize', + schema: { + inputs: [{ inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Size', dataType: 'Number', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.size`; + }, +}); + +registerNode({ + id: 'MapClear', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [{ inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Map', dataType: 'Data', outOrder: 1 }], + color: 'Obj', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `${compiler.handleInputs(inputPins[0])}.clear();\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}`; + }, +}); + +registerNode({ + id: 'MapKeys', + schema: { + inputs: [{ inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Keys', dataType: 'Array', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `[...${compiler.handleInputs(inputPins[0])}.keys()]`; + }, +}); + +registerNode({ + id: 'MapValues', + schema: { + inputs: [{ inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Values', dataType: 'Array', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `[...${compiler.handleInputs(inputPins[0])}.values()]`; + }, +}); + +registerNode({ + id: 'MapEntries', + schema: { + inputs: [{ inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Entries', dataType: 'Array', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `[...${compiler.handleInputs(inputPins[0])}.entries()]`; + }, +}); + +registerNode({ + id: 'MapFromObject', + schema: { + inputs: [{ inputTitle: 'Object', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Map', dataType: 'Data', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 14, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `new Map(Object.entries(${compiler.handleInputs(inputPins[0])}))`; + }, +}); + +registerNode({ + id: 'MapToObject', + schema: { + inputs: [{ inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Object', dataType: 'Data', outOrder: 0 }], + color: 'Obj', + rows: 2, + colums: 13, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `Object.fromEntries(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'ForEachMap', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 3 }, + ], + inputs: [{ inputTitle: 'Map', dataType: 'Data', isInputBoxRequired: false }], + outputs: [ + { outputTitle: 'Key', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Value', dataType: 'Data', outOrder: 2 }, + ], + color: 'Obj', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const v = `_fem${node._id}`; + compiler.script += `${compiler.handleInputs(inputPins[0])}.forEach((val${v}, key${v}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const v = `_fem${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 0) return `key${v}`; + return `val${v}`; + }, +}); + +// ---------- Clone/Array: DeepClone, CloneArray, Slice, Splice, FlattenArray, Spread(Array), Includes(Arr), Unique ---------- +registerNode({ + id: 'DeepClone', + schema: { + inputs: [{ inputTitle: 'Value', dataType: 'Data', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Clone', dataType: 'Data', outOrder: 0 }], + color: 'Func', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `structuredClone(${compiler.handleInputs(inputPins[0])})`; + }, +}); + +registerNode({ + id: 'CloneArray', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Clone', dataType: 'Array', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 12, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `[...${compiler.handleInputs(inputPins[0])}]`; + }, +}); + +registerNode({ + id: 'Slice', + schema: { + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Start', dataType: 'Number', defValue: 0 }, + { inputTitle: 'End', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Array', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.slice(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[2])})`; + }, +}); + +registerNode({ + id: 'Splice', + schema: { + execIn: true, + execOut: [{ outOrder: 0 }], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Start', dataType: 'Number', defValue: 0 }, + { inputTitle: 'DelCount', dataType: 'Number', defValue: 1 }, + ], + outputs: [ + { outputTitle: 'Array', dataType: 'Array', outOrder: 1 }, + { outputTitle: 'Removed', dataType: 'Array', outOrder: 2 }, + ], + color: 'Get', + rows: 4, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + compiler.script += `let _spliced${node._id} = ${compiler.handleInputs(inputPins[0])}.splice(${compiler.handleInputs(inputPins[1])}, ${compiler.handleInputs(inputPins[2])});\n`; + compiler.coreAlgorithm(execOutPins[0]); + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + if (inputNode.srcOutputPinNumber == 0) return `${compiler.handleInputs(inputPins[0])}`; + return `_spliced${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'FlattenArray', + schema: { + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Depth', dataType: 'Number', defValue: 1 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Array', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 13, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.flat(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Spread(Array)', + schema: { + inputs: [ + { inputTitle: 'ArrayA', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'ArrayB', dataType: 'Array', isInputBoxRequired: false }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Array', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 14, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `[...${compiler.handleInputs(inputPins[0])}, ...${compiler.handleInputs(inputPins[1])}]`; + }, +}); + +registerNode({ + id: 'Includes(Arr)', + schema: { + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Value', dataType: 'Data', defValue: 0 }, + ], + outputs: [{ outputTitle: 'Result', dataType: 'Boolean', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 14, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `${compiler.handleInputs(inputPins[0])}.includes(${compiler.handleInputs(inputPins[1])})`; + }, +}); + +registerNode({ + id: 'Unique', + schema: { + inputs: [{ inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }], + outputs: [{ outputTitle: 'Result', dataType: 'Array', outOrder: 0 }], + color: 'Get', + rows: 2, + colums: 10, + }, + exprCodegen(compiler, inputNode) { + const inputPins = compiler.getInputPins(inputNode.node); + return `[...new Set(${compiler.handleInputs(inputPins[0])})]`; + }, +}); + +// ---------- Higher-Order Array: Filter, ArrayMap, Reduce, Find, Every, Some ---------- +registerNode({ + id: 'Filter', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 4 }, + ], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Keep?', dataType: 'Boolean', defValue: true }, + ], + outputs: [ + { outputTitle: 'Element', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 2 }, + { outputTitle: 'Result', dataType: 'Array', outOrder: 3 }, + ], + color: 'Get', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const v = `_fl${node._id}`; + compiler.script += `let _flResult${node._id} = ${compiler.handleInputs(inputPins[0])}.filter((val${v}, idx${v}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `return ${compiler.handleInputs(inputPins[1])};\n});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const v = `_fl${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 0) return `val${v}`; + if (inputNode.srcOutputPinNumber == 1) return `idx${v}`; + return `_flResult${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'ArrayMap', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 4 }, + ], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Mapped', dataType: 'Data', defValue: 0, isInputBoxRequired: false }, + ], + outputs: [ + { outputTitle: 'Element', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 2 }, + { outputTitle: 'Result', dataType: 'Array', outOrder: 3 }, + ], + color: 'Get', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const v = `_mp${node._id}`; + compiler.script += `let _mpResult${node._id} = ${compiler.handleInputs(inputPins[0])}.map((val${v}, idx${v}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `return ${compiler.handleInputs(inputPins[1])};\n});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const v = `_mp${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 0) return `val${v}`; + if (inputNode.srcOutputPinNumber == 1) return `idx${v}`; + return `_mpResult${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'Reduce', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 5 }, + ], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Initial', dataType: 'Data', defValue: 0 }, + { inputTitle: 'NextAcc', dataType: 'Data', defValue: 0, isInputBoxRequired: false }, + ], + outputs: [ + { outputTitle: 'Accum', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Element', dataType: 'Data', outOrder: 2 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 3 }, + { outputTitle: 'Result', dataType: 'Data', outOrder: 4 }, + ], + color: 'Get', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const v = `_rd${node._id}`; + compiler.script += `let _rdResult${node._id} = ${compiler.handleInputs(inputPins[0])}.reduce((acc${v}, val${v}, idx${v}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `return ${compiler.handleInputs(inputPins[2])};\n}, ${compiler.handleInputs(inputPins[1])});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const v = `_rd${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 0) return `acc${v}`; + if (inputNode.srcOutputPinNumber == 1) return `val${v}`; + if (inputNode.srcOutputPinNumber == 2) return `idx${v}`; + return `_rdResult${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'Find', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 5 }, + ], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Match?', dataType: 'Boolean', defValue: true }, + ], + outputs: [ + { outputTitle: 'Element', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 2 }, + { outputTitle: 'Result', dataType: 'Data', outOrder: 3 }, + { outputTitle: 'FoundIdx', dataType: 'Number', outOrder: 4 }, + ], + color: 'Get', + rows: 2, + colums: 12, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const v = `_fnd${node._id}`; + compiler.script += `let _fndResult${node._id};\nlet _fndIdx${node._id} = -1;\n`; + compiler.script += `${compiler.handleInputs(inputPins[0])}.some((val${v}, idx${v}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `if (${compiler.handleInputs(inputPins[1])}) { _fndResult${node._id} = val${v}; _fndIdx${node._id} = idx${v}; return true; }\nreturn false;\n});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const v = `_fnd${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 0) return `val${v}`; + if (inputNode.srcOutputPinNumber == 1) return `idx${v}`; + if (inputNode.srcOutputPinNumber == 2) return `_fndResult${inputNode.node._id}`; + return `_fndIdx${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'Every', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 4 }, + ], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Match?', dataType: 'Boolean', defValue: true }, + ], + outputs: [ + { outputTitle: 'Element', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 2 }, + { outputTitle: 'Result', dataType: 'Boolean', outOrder: 3 }, + ], + color: 'Get', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const v = `_ev${node._id}`; + compiler.script += `let _evResult${node._id} = ${compiler.handleInputs(inputPins[0])}.every((val${v}, idx${v}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `return ${compiler.handleInputs(inputPins[1])};\n});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const v = `_ev${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 0) return `val${v}`; + if (inputNode.srcOutputPinNumber == 1) return `idx${v}`; + return `_evResult${inputNode.node._id}`; + }, +}); + +registerNode({ + id: 'Some', + schema: { + execIn: true, + execOut: [ + { execOutTitle: 'Loop Body', outOrder: 0 }, + { execOutTitle: 'Completed', outOrder: 4 }, + ], + inputs: [ + { inputTitle: 'Array', dataType: 'Array', isInputBoxRequired: false }, + { inputTitle: 'Match?', dataType: 'Boolean', defValue: true }, + ], + outputs: [ + { outputTitle: 'Element', dataType: 'Data', outOrder: 1 }, + { outputTitle: 'Index', dataType: 'Number', outOrder: 2 }, + { outputTitle: 'Result', dataType: 'Boolean', outOrder: 3 }, + ], + color: 'Get', + rows: 2, + colums: 10, + }, + execCodegen(compiler, node) { + const inputPins = compiler.getInputPins(node); + const execOutPins = compiler.getExecOut(node); + const v = `_sm${node._id}`; + compiler.script += `let _smResult${node._id} = ${compiler.handleInputs(inputPins[0])}.some((val${v}, idx${v}) => {\n`; + compiler.coreAlgorithm(execOutPins[0]); + compiler.script += `return ${compiler.handleInputs(inputPins[1])};\n});\n`; + compiler.coreAlgorithm(execOutPins[1]); + }, + exprCodegen(compiler, inputNode) { + const v = `_sm${inputNode.node._id}`; + if (inputNode.srcOutputPinNumber == 0) return `val${v}`; + if (inputNode.srcOutputPinNumber == 1) return `idx${v}`; + return `_smResult${inputNode.node._id}`; + }, +}); + +// ---------- Menu order (null = separator) ---------- +// Categories: Begin(Flow), Print(I/O), Logic, Math, Str(String), Obj(Object/Map), Get(Array), Func(Utility) +registerMenuOrder([ + // Flow + 'Begin', null, + // I/O + 'Print', 'Alert', 'Confirm', 'Prompt', null, + // Logic: control flow, comparison, boolean ops + 'If/Else', 'Try/Catch', 'WhileLoop', 'ForLoop', 'ForEachLoop', 'Continue', 'Break', 'Assert', 'Sleep', + 'Equals', 'Not Equals', 'Less', 'LessEq', 'Greater', 'GreaterEq', + 'AND', 'OR', 'XOR', 'NEG', 'Ternary', null, + // Math: arithmetic, rounding, bitwise + 'Add', 'Subtract', 'Multiply', 'Divide', 'Modulo', 'Power', 'Random', + 'Ceil', 'Floor', 'Round', 'Abs', 'Clamp(Num)', 'Max(Num)', 'Min(Num)', + 'bAND', 'bOR', 'bXOR', 'bNEG', null, + // String + 'Concat', 'StringLength', 'Substring', 'CharAt', + 'IndexOf(Str)', 'LastIndexOf(Str)', 'Replace', 'ReplaceAll', + 'Split', 'Join', 'Trim', 'ToUpperCase', 'ToLowerCase', + 'StartsWith', 'EndsWith', 'Includes(Str)', 'Repeat', + 'ParseInt', 'ParseFloat', 'ToString', 'StrToArray', 'ArrayToStr', null, + // Object / Map + 'JSON Parse', 'JSON Stringify', 'CreateObject', 'SetProperty', 'GetProperty', 'GetByName(JSON)', 'DeleteProperty', + 'HasProperty', 'ObjectKeys', 'ObjectValues', 'ObjectEntries', 'FromEntries', + 'MergeObjects', 'ObjectSize', 'TypeOf', 'IsNull', 'IsArray', + 'CreateMap', 'MapSet', 'MapGet', 'MapHas', 'MapDelete', 'MapSize', 'MapClear', + 'MapKeys', 'MapValues', 'MapEntries', 'MapFromObject', 'MapToObject', 'ForEachMap', null, + // Array + 'Length', 'Front', 'Back', 'GetByPos', 'SetByPos', + 'Search', 'BinarySearch(Num)', 'Includes(Arr)', + 'PushBack', 'PopBack', 'PushFront', 'PopFront', 'Insert', + 'Sort(Num)', 'isEmpty', 'Reverse', 'Max(Array)', 'Min(Array)', + 'Slice', 'Splice', 'Spread(Array)', 'FlattenArray', 'CloneArray', 'Unique', + 'Filter', 'ArrayMap', 'Reduce', 'Find', 'Every', 'Some', null, + // Utility + 'Swap', 'DeepClone', 'OpenWindow', 'HttpRequest', null, +]); diff --git a/src/js/registry/registry.js b/src/js/registry/registry.js new file mode 100644 index 0000000..448ca04 --- /dev/null +++ b/src/js/registry/registry.js @@ -0,0 +1,164 @@ +/** + * Central node registry: single source of truth for node types. + * Register a node once with schema + codegen; it drives the context menu, node creation, and compiler. + */ + +const definitions = new Map(); +const menuOrder = []; + +/** + * Build full nodeDescription (with null pin ids) from a compact schema. + * Schema shape: + * - nodeTitle (id string) + * - execIn?: boolean + * - execOut?: Array<{ execOutTitle?: string }> (outOrder = index) + * - inputs?: Array<{ inputTitle, dataType, defValue }> + * - outputs?: Array<{ outputTitle, dataType, outOrder }> + * - color, rows, colums + */ +function schemaToNodeDescription(schema) { + const nodeDescription = { + nodeTitle: schema.nodeTitle, + color: schema.color, + rows: schema.rows, + colums: schema.colums, + }; + if (schema.execIn !== undefined) { + nodeDescription.execIn = schema.execIn; + nodeDescription.pinExecInId = null; + } + if (schema.execOut && schema.execOut.length > 0) { + nodeDescription.execOut = {}; + schema.execOut.forEach((out, i) => { + nodeDescription.execOut[`execOut${i}`] = { + execOutTitle: out.execOutTitle ?? null, + pinExecOutId: null, + outOrder: out.outOrder ?? i, + }; + }); + } + if (schema.inputs && schema.inputs.length > 0) { + nodeDescription.inputs = {}; + schema.inputs.forEach((inp, i) => { + nodeDescription.inputs[`input${i}`] = { + inputTitle: inp.inputTitle, + dataType: inp.dataType, + defValue: inp.defValue !== undefined ? inp.defValue : null, + pinInId: null, + isInputBoxRequired: inp.isInputBoxRequired !== false, + }; + }); + } + if (schema.outputs && schema.outputs.length > 0) { + nodeDescription.outputs = {}; + schema.outputs.forEach((out, i) => { + nodeDescription.outputs[`output${i}`] = { + outputTitle: out.outputTitle, + dataType: out.dataType, + pinOutId: null, + outOrder: out.outOrder ?? i, + }; + }); + } + return nodeDescription; +} + +/** + * Register a node type. All node definitions live here (or in files that call this). + * @param {Object} def - { id, label?, schema, execCodegen?, exprCodegen? } + * - id: unique string (e.g. 'Print') + * - label: display name in context menu (default: id) + * - schema: compact schema for nodeDescription (nodeTitle, execIn, execOut, inputs, outputs, color, rows, colums) + * - execCodegen(compiler, node): called for execution flow (append to compiler.script, call compiler.coreAlgorithm) + * - exprCodegen(compiler, inputNode): optional; called for expression value (return string) + */ +export function registerNode(def) { + const id = def.id; + const schema = { ...def.schema, nodeTitle: id }; + definitions.set(id, { + id, + label: def.label ?? id, + schema, + execCodegen: def.execCodegen ?? null, + exprCodegen: def.exprCodegen ?? null, + }); + return id; +} + +/** + * Register a menu separator (no node, just order). Call with registerNode({ id: null, menuOnly: true }) or add a separate API. + * We use menuOrder array: push null for separator when registering. So we need registerSeparator() or we add to menuOrder in definitions.js. + */ +export function registerMenuOrder(orderedIdsAndNulls) { + menuOrder.length = 0; + menuOrder.push(...orderedIdsAndNulls); +} + +/** + * Get full nodeDescription for a registered type (for Nodes.CreateNode). Returns null if not found. + */ +export function buildNodeDescription(type) { + const def = definitions.get(type); + if (!def) return null; + return schemaToNodeDescription(def.schema); +} + +/** + * Get registry definition for a type. + */ +export function getDefinition(type) { + return definitions.get(type) ?? null; +} + +/** + * Get ordered list of menu entries: array of node ids (string) or null for separator. + */ +export function getMenuOrder() { + return [...menuOrder]; +} + +/** + * Get menu order grouped by category (schema.color). Preserves order: categoryOrder lists + * categories in first-appearance order; groups[category] lists node ids in menu order. + * @returns {{ categoryOrder: string[], groups: Record }} + */ +export function getMenuOrderGroupedByCategory() { + const categoryOrder = []; + const groups = Object.create(null); + for (const id of menuOrder) { + if (id === null) continue; + const def = definitions.get(id); + if (!def || !def.schema || !def.schema.color) continue; + const cat = def.schema.color; + if (!groups[cat]) { + categoryOrder.push(cat); + groups[cat] = []; + } + groups[cat].push(id); + } + return { categoryOrder, groups }; +} + +/** + * Run exec codegen for a node type. No-op if no execCodegen. + */ +export function runExecCodegen(type, compiler, node) { + const def = definitions.get(type); + if (def && def.execCodegen) def.execCodegen(compiler, node); +} + +/** + * Run expr codegen for a node type. Returns undefined if no exprCodegen (caller should handle). + */ +export function runExprCodegen(type, compiler, inputNode) { + const def = definitions.get(type); + if (def && def.exprCodegen) return def.exprCodegen(compiler, inputNode); + return undefined; +} + +/** + * Check if a type is registered (built-in node). Get/Set are not registered. + */ +export function hasType(type) { + return definitions.has(type); +} diff --git a/src/js/ui/dialogs.js b/src/js/ui/dialogs.js new file mode 100644 index 0000000..d7a0b7c --- /dev/null +++ b/src/js/ui/dialogs.js @@ -0,0 +1,116 @@ +import { refresh } from '../persistence/saveAndLoad.js' +import { Import } from '../persistence/saveAndLoad.js' + +export function showAlert(msg) { + let alertMsg = document.getElementById("alert-box").children[0].children[0]; + let alertBox = document.getElementById("alert-box"); + document.getElementById("alert-ok-btn").addEventListener("click", (e) => { + alertBox.classList.toggle("hidden", true); + }); + alertMsg.innerHTML = `Alert: ${msg}`; + alertBox.classList.toggle('hidden', false); + [...document.getElementsByClassName("sidebox")].forEach(value => { + if (value !== alertBox) { + value.classList.toggle("hidden", true); + } + else { + value.classList.toggle("hidden", false); + } + }) +} + +export function showConfirm(msg, onConfirm) { + const dialog = document.getElementById("confirm-dialog"); + const msgEl = document.getElementById("confirm-dialog-msg"); + const okBtn = document.getElementById("confirm-dialog-ok"); + const cancelBtn = document.getElementById("confirm-dialog-cancel"); + + msgEl.innerHTML = msg; + + [...document.getElementsByClassName("sidebox")].forEach(value => { + if (value !== dialog) { + value.classList.toggle("hidden", true); + } + }); + dialog.classList.toggle("hidden", false); + + const cleanup = () => { + dialog.classList.toggle("hidden", true); + okBtn.removeEventListener("click", handleOk); + cancelBtn.removeEventListener("click", handleCancel); + }; + const handleOk = () => { + cleanup(); + onConfirm(); + }; + const handleCancel = () => { + cleanup(); + }; + okBtn.addEventListener("click", handleOk); + cancelBtn.addEventListener("click", handleCancel); +} + +//
      Alert: Current Scipt Will Be Lost Unless Exported
      + +export function prompRefreshOrStarter(type, stage) { + let refreshBox = document.getElementById("refresh-box"); + let refBtn = document.getElementById("refresh-btn"); + let refCnclBtn = document.getElementById("refresh-cancel-btn"); + // console.log("refresh clicked"); + if (type == 'refresh') { + refreshBox.children[0].children[1].innerHTML = 'Refresh' + refreshBox.classList.toggle('hidden', false); + [...document.getElementsByClassName("sidebox")].forEach(value => { + if (value !== refreshBox) { + value.classList.toggle("hidden", true); + } + else { + value.classList.toggle("hidden", false); + } + }); + refBtn.addEventListener("click", (e) => { + refresh(stage.findOne("#main_layer"), stage.findOne("#wireLayer")); + refreshBox.classList.toggle('hidden', true); + }); + refCnclBtn.addEventListener("click", (e) => { + refreshBox.classList.toggle('hidden', true); + }); + } + if (type == 'starter') { + refreshBox.children[0].children[1].innerHTML = 'Load'; + refreshBox.classList.toggle('hidden', false); + [...document.getElementsByClassName("sidebox")].forEach(value => { + if (value !== refreshBox) { + value.classList.toggle("hidden", true); + } + else { + value.classList.toggle("hidden", false); + } + }); + refBtn.addEventListener("click", (e) => { + refreshBox.classList.toggle('hidden', true); + vscriptOnLoad(stage); + }); + refCnclBtn.addEventListener("click", (e) => { + refreshBox.classList.toggle('hidden', true); + }) ; + } +} +const STARTER_FILE_PATH = 'assets/starter.json'; + +export async function vscriptOnLoad(stage) { + const layer = stage.findOne('#main_layer'); + const wireLayer = stage.findOne('#wireLayer'); + try { + const res = await fetch(STARTER_FILE_PATH); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const starterJSON = await res.text(); + new Import(stage, layer, wireLayer, starterJSON); + } catch (err) { + console.error('Failed to load starter.json:', err); + showAlert('Could not load starter.json. Using empty project.'); + refresh(layer, wireLayer); + } +} + diff --git a/src/js/ui/functionPanel.js b/src/js/ui/functionPanel.js new file mode 100644 index 0000000..d130d2a --- /dev/null +++ b/src/js/ui/functionPanel.js @@ -0,0 +1,520 @@ +import { colorMap } from '../core/colorMap.js'; +import { tabManager, isValidFunctionName } from '../editor/tabManager.js'; +import { applyOrphanOverlay } from '../editor/orphanOverlay.js'; +import { showAlert } from './dialogs.js'; +import { Nodes } from '../nodes/nodeFactory.js'; +import { showDocTooltip, hideDocTooltip } from '../utils/docTooltip.js'; + +const DATA_TYPES = ['Number', 'Boolean', 'String', 'Array']; + +function createDefaultValueInput(dataType, currentValue) { + if (dataType === 'Boolean') { + const sel = document.createElement('select'); + sel.className = 'param-def-value'; + const optT = document.createElement('option'); + optT.value = 'true'; + optT.textContent = 'True'; + const optF = document.createElement('option'); + optF.value = 'false'; + optF.textContent = 'False'; + sel.appendChild(optT); + sel.appendChild(optF); + if (currentValue === 'false') optF.selected = true; + return sel; + } + const inp = document.createElement('input'); + inp.className = 'param-def-value'; + inp.type = dataType === 'Number' ? 'number' : 'text'; + if (dataType === 'Number') inp.value = currentValue != null ? currentValue : 0; + else if (dataType === 'String') { + let display = currentValue != null ? currentValue : ''; + if (typeof display === 'string' && display.startsWith("'") && display.endsWith("'")) display = display.slice(1, -1); + inp.value = display; + } else if (dataType === 'Array') inp.value = currentValue || '[]'; + inp.placeholder = dataType === 'Array' ? '[1, 2, 3]' : 'Default value'; + return inp; +} + +function extractDefValue(dataType, el) { + const raw = el.value; + if (dataType === 'Boolean') return raw === 'true'; + if (dataType === 'Number') return raw.toString(); + if (dataType === 'String') return `'${raw}'`; + if (dataType === 'Array') return raw; + return raw; +} + +function buildParamForm(sectionId, onAdd, options = {}) { + const { includeDefaultValue: includeDef = false } = options; + const content = document.getElementById(sectionId); + if (!content) return; + content.innerHTML = ''; + + const sectionInner = document.createElement('div'); + sectionInner.className = 'left-panel-section-inner'; + + const form = document.createElement('div'); + form.className = 'var-inline-edit'; + + const typeSelect = document.createElement('select'); + for (const t of DATA_TYPES) { + const opt = document.createElement('option'); + opt.value = t; + opt.textContent = t; + typeSelect.appendChild(opt); + } + + const nameInput = document.createElement('input'); + nameInput.type = 'text'; + nameInput.placeholder = 'Name'; + + const defValueContainer = document.createElement('div'); + defValueContainer.className = 'param-def-value-container'; + let defValueEl = null; + if (includeDef) { + defValueEl = createDefaultValueInput(typeSelect.value, null); + defValueContainer.appendChild(defValueEl); + typeSelect.addEventListener('change', () => { + defValueContainer.innerHTML = ''; + defValueEl = createDefaultValueInput(typeSelect.value); + defValueContainer.appendChild(defValueEl); + }); + } + + const actionsDiv = document.createElement('div'); + actionsDiv.classList.add('var-edit-actions'); + const addBtn = document.createElement('button'); + addBtn.classList.add('var-edit-save'); + addBtn.textContent = 'Add'; + addBtn.addEventListener('click', () => { + const name = nameInput.value.trim(); + const dataType = typeSelect.value; + if (!name) { showAlert("Parameter name can't be empty!"); return; } + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) { + showAlert("Parameter name must be a valid identifier"); return; + } + const param = { name, dataType }; + if (includeDef && defValueEl) { + param.defValue = extractDefValue(dataType, defValueEl); + } + onAdd(param); + nameInput.value = ''; + if (includeDef && defValueEl) { + defValueContainer.innerHTML = ''; + defValueEl = createDefaultValueInput(typeSelect.value, null); + defValueContainer.appendChild(defValueEl); + } + }); + actionsDiv.appendChild(addBtn); + + form.appendChild(typeSelect); + form.appendChild(nameInput); + if (includeDef) form.appendChild(defValueContainer); + form.appendChild(actionsDiv); + sectionInner.appendChild(form); + + const listEl = document.createElement('ul'); + listEl.className = 'param-list'; + sectionInner.appendChild(listEl); + content.appendChild(sectionInner); + + return listEl; +} + +function renderParamList(listEl, params, onRemove, onEdit) { + listEl.innerHTML = ''; + params.forEach((param, idx) => { + const li = document.createElement('li'); + li.className = 'left-panel-variable'; + li.style.borderWidth = '2px'; + li.style.borderStyle = 'solid'; + li.style.borderColor = colorMap[param.dataType] || '#fff'; + li.style.boxShadow = `inset 0px 0px 5px ${colorMap[param.dataType] || '#fff'}`; + li.style.backgroundColor = 'transparent'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'var-name-text'; + nameSpan.textContent = param.name; + nameSpan.style.overflow = 'hidden'; + nameSpan.style.textOverflow = 'ellipsis'; + nameSpan.style.whiteSpace = 'nowrap'; + nameSpan.style.flex = '1'; + li.appendChild(nameSpan); + + const typeSpan = document.createElement('span'); + typeSpan.style.fontSize = '0.85rem'; + typeSpan.style.opacity = '0.6'; + typeSpan.style.marginLeft = '0.3rem'; + typeSpan.textContent = param.dataType; + li.appendChild(typeSpan); + + if (param.defValue != null && param.defValue !== '') { + const defSpan = document.createElement('span'); + defSpan.style.fontSize = '0.75rem'; + defSpan.style.opacity = '0.5'; + defSpan.style.marginLeft = '0.3rem'; + const display = typeof param.defValue === 'string' && param.defValue.startsWith("'") && param.defValue.endsWith("'") + ? param.defValue.slice(1, -1) : String(param.defValue); + defSpan.textContent = `= ${display}`; + defSpan.title = 'Default value'; + li.appendChild(defSpan); + } + + const actionsDiv = document.createElement('div'); + actionsDiv.classList.add('var-actions'); + + const deleteBtn = document.createElement('button'); + deleteBtn.classList.add('var-delete-btn'); + deleteBtn.innerHTML = ''; + deleteBtn.title = 'Remove parameter'; + deleteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + onRemove(idx); + }); + actionsDiv.appendChild(deleteBtn); + li.appendChild(actionsDiv); + + li.addEventListener('mouseover', () => { + li.style.boxShadow = `inset 0px 0px 30px ${colorMap[param.dataType] || '#fff'}`; + }); + li.addEventListener('mouseleave', () => { + li.style.boxShadow = `inset 0px 0px 5px ${colorMap[param.dataType] || '#fff'}`; + }); + + listEl.appendChild(li); + }); +} + +let inputListEl = null; +let outputListEl = null; + +function refreshInputList() { + const tab = tabManager.getActiveTab(); + if (!tab || tab.type !== 'function' || !inputListEl) return; + renderParamList(inputListEl, tab.inputParams, (idx) => { + tab.inputParams.splice(idx, 1); + refreshInputList(); + tabManager._emit('functionParamsChanged', { tab, paramType: 'input' }); + }); +} + +function refreshOutputList() { + const tab = tabManager.getActiveTab(); + if (!tab || tab.type !== 'function' || !outputListEl) return; + renderParamList(outputListEl, tab.outputParams, (idx) => { + tab.outputParams.splice(idx, 1); + refreshOutputList(); + tabManager._emit('functionParamsChanged', { tab, paramType: 'output' }); + }); +} + +export function initFunctionPanel() { + document.getElementById('input-params').addEventListener('click', () => { + document.getElementById('input-params-content').classList.toggle('hidden'); + document.getElementById('input-params-arrow').classList.toggle('left-panel-tab-arrow-up'); + }); + + document.getElementById('output-params').addEventListener('click', () => { + document.getElementById('output-params-content').classList.toggle('hidden'); + document.getElementById('output-params-arrow').classList.toggle('left-panel-tab-arrow-up'); + }); + + document.getElementById('doc-string').addEventListener('click', () => { + document.getElementById('doc-string-content').classList.toggle('hidden'); + document.getElementById('doc-string-arrow').classList.toggle('left-panel-tab-arrow-up'); + }); + + const docStringInput = document.getElementById('doc-string-input'); + if (docStringInput) { + docStringInput.addEventListener('input', () => { + const tab = tabManager.getActiveTab(); + if (!tab || tab.type !== 'function') return; + tab.docString = docStringInput.value.trim(); + }); + } + + const saveFunctionBtn = document.getElementById('save-function-btn'); + if (saveFunctionBtn) { + saveFunctionBtn.addEventListener('click', () => { + const tab = tabManager.getActiveTab(); + if (!tab || tab.type !== 'function') return; + tabManager.saveFunction(tab.id); + }); + } + + document.getElementById('list-of-functions').addEventListener('click', () => { + document.getElementById('list-of-functions-content').classList.toggle('hidden'); + document.getElementById('list-of-functions-down-icon').classList.toggle('left-panel-tab-arrow-up'); + }); + + inputListEl = buildParamForm('input-params-content', (param) => { + const tab = tabManager.getActiveTab(); + if (!tab || tab.type !== 'function') return; + if (tab.inputParams.some(p => p.name === param.name)) { + showAlert('Input parameter already exists'); return; + } + tab.inputParams.push(param); + refreshInputList(); + tabManager._emit('functionParamsChanged', { tab, paramType: 'input' }); + }, { includeDefaultValue: true }); + + outputListEl = buildParamForm('output-params-content', (param) => { + const tab = tabManager.getActiveTab(); + if (!tab || tab.type !== 'function') return; + if (tab.outputParams.some(p => p.name === param.name)) { + showAlert('Output parameter already exists'); return; + } + tab.outputParams.push(param); + refreshOutputList(); + tabManager._emit('functionParamsChanged', { tab, paramType: 'output' }); + }); + + tabManager.on('tabSwitched', ({ to }) => { + updatePanelVisibility(to); + }); + + tabManager.on('tabCreated', (tab) => { + updatePanelVisibility(tab); + if (tab.type === 'function' && !tabManager._suppressAutoNodes) { + createFunctionNodes(tab); + } + }); + + tabManager.on('tabClosed', (tab) => { + if (tab && tab.type === 'function') { + const stage = tabManager.getStage(); + const remainingTabs = tabManager.getAllTabs(); + for (const t of remainingTabs) { + const layer = t.layer; + if (!layer) continue; + layer.find('.aProgramNodeGroup').forEach((grp) => { + const cc = grp.customClass; + if (!cc || !cc.nodeDescription || !cc.nodeDescription.isCallFunction || cc.nodeDescription.calledFunctionName !== tab.name) return; + if (cc.isOrphaned) return; + applyOrphanOverlay(grp, layer, stage, 'Deleted Func'); + }); + } + if (stage) stage.draw(); + } + refreshFunctionsList(); + refreshContextMenuFunctions(); + }); + + tabManager.on('tabRenamed', () => { + refreshFunctionsList(); + refreshContextMenuFunctions(); + }); + + tabManager.on('functionParamsChanged', ({ tab, paramType }) => { + syncFunctionNodes(tab, paramType); + }); + + tabManager.on('functionSaved', (tab) => { + Nodes.updateCallNodesToDefinition(tab.name, tab.saved.inputParams, tab.saved.outputParams, tab.saved.docString); + Nodes.updateCallNodesDocString(tab.name, tab.saved.docString); + refreshFunctionsList(); + refreshContextMenuFunctions(); + }); +} + +function createFunctionNodes(tab) { + const stage = tabManager.getStage(); + const containerRect = stage.container().getBoundingClientRect(); + const beginX = containerRect.width * 0.2; + const beginY = containerRect.height * 0.3; + const returnX = containerRect.width * 0.7; + const returnY = containerRect.height * 0.3; + + const beginNode = Nodes.CreateFunctionBeginNode( + tab.inputParams, { x: beginX, y: beginY }, tab.layer, stage + ); + tab.beginNodeId = beginNode.grp.id(); + + const returnNode = Nodes.CreateReturnNode( + tab.outputParams, { x: returnX, y: returnY }, tab.layer, stage + ); + tab.returnNodeId = returnNode.grp.id(); +} + +function syncFunctionNodes(tab, paramType) { + const stage = tabManager.getStage(); + const wireLayer = tab.wireLayer; + + if (paramType === 'input') { + const beginGrp = tab.layer.findOne('#FunctionBegin') || tab.layer.findOne(`#${tab.beginNodeId}`); + if (beginGrp && beginGrp.customClass) { + const newNode = Nodes.rebuildFunctionBeginNode(beginGrp.customClass, tab.inputParams, tab.layer, stage, wireLayer); + tab.beginNodeId = newNode.grp.id(); + } + } else if (paramType === 'output') { + const returnGrp = tab.layer.findOne('#Return') || tab.layer.findOne(`#${tab.returnNodeId}`); + if (returnGrp && returnGrp.customClass) { + const newNode = Nodes.rebuildReturnNode(returnGrp.customClass, tab.outputParams, tab.layer, stage, wireLayer); + tab.returnNodeId = newNode.grp.id(); + } + } +} + +function updatePanelVisibility(tab) { + const inputSection = document.getElementById('input-params-section'); + const outputSection = document.getElementById('output-params-section'); + const docSection = document.getElementById('doc-string-section'); + const docStringInput = document.getElementById('doc-string-input'); + const saveFunctionSection = document.getElementById('save-function-section'); + + if (tab.type === 'function') { + inputSection.classList.remove('hidden'); + outputSection.classList.remove('hidden'); + if (docSection) docSection.classList.remove('hidden'); + if (saveFunctionSection) saveFunctionSection.classList.remove('hidden'); + if (docStringInput) docStringInput.value = tab.docString || ''; + refreshInputList(); + refreshOutputList(); + } else { + inputSection.classList.add('hidden'); + outputSection.classList.add('hidden'); + if (docSection) docSection.classList.add('hidden'); + if (saveFunctionSection) saveFunctionSection.classList.add('hidden'); + } +} + +function refreshFunctionsList() { + const listEl = document.getElementById('function-list'); + if (!listEl) return; + listEl.innerHTML = ''; + + const funcTabs = tabManager.getAllSavedFunctionTabs(); + for (const ft of funcTabs) { + const saved = ft.saved; + const li = document.createElement('li'); + li.className = 'left-panel-variable'; + li.style.borderWidth = '2px'; + li.style.borderStyle = 'solid'; + li.style.borderColor = colorMap['Call'] || '#00bfa5'; + li.style.boxShadow = `inset 0px 0px 5px ${colorMap['Call'] || '#00bfa5'}`; + li.style.backgroundColor = 'transparent'; + li.setAttribute('draggable', 'true'); + + const nameSpan = document.createElement('span'); + nameSpan.className = 'var-name-text'; + nameSpan.textContent = saved.name; + nameSpan.style.overflow = 'hidden'; + nameSpan.style.textOverflow = 'ellipsis'; + nameSpan.style.whiteSpace = 'nowrap'; + nameSpan.style.flex = '1'; + li.appendChild(nameSpan); + + let docIcon = null; + if (saved.docString) { + docIcon = document.createElement('span'); + docIcon.className = 'function-list-doc-icon'; + docIcon.textContent = 'i'; + docIcon.title = 'Documentation'; + li.appendChild(docIcon); + } + + const infoSpan = document.createElement('span'); + infoSpan.style.fontSize = '0.8rem'; + infoSpan.style.opacity = '0.5'; + infoSpan.style.marginLeft = '0.3rem'; + const inCount = saved.inputParams.length; + const outCount = saved.outputParams.length; + infoSpan.textContent = `(${inCount}→${outCount})`; + li.appendChild(infoSpan); + + li.addEventListener('mouseover', () => { + li.style.boxShadow = `inset 0px 0px 30px ${colorMap['Call'] || '#00bfa5'}`; + }); + li.addEventListener('mouseleave', () => { + li.style.boxShadow = `inset 0px 0px 5px ${colorMap['Call'] || '#00bfa5'}`; + if (docIcon) hideDocTooltip(docIcon); + }); + + if (docIcon) { + li.addEventListener('mouseenter', (e) => { + showDocTooltip(docIcon, saved.docString); + }); + } + + li.addEventListener('dblclick', () => { + tabManager.switchTab(ft.id); + }); + + li.addEventListener('dragstart', (e) => { + e.dataTransfer.setData('functionTabId', ft.id); + e.dataTransfer.setData('functionName', ft.name); + }); + + listEl.appendChild(li); + } +} + +function refreshContextMenuFunctions() { + let section = document.getElementById('context-menu-functions-section'); + const contextMenu = document.getElementById('context-menu'); + if (!contextMenu) return; + + if (section) { + section.remove(); + } + + const funcTabs = tabManager.getAllSavedFunctionTabs(); + if (funcTabs.length === 0) return; + + section = document.createElement('div'); + section.className = 'ctx-menu-section'; + section.id = 'context-menu-functions-section'; + section.dataset.category = 'Functions'; + + const callColor = colorMap['Call'] || '#00bfa5'; + const header = document.createElement('div'); + header.className = 'ctx-menu-section-header'; + header.style.borderLeftColor = callColor; + header.style.color = callColor; + + const arrow = document.createElement('span'); + arrow.className = 'ctx-menu-section-arrow'; + arrow.textContent = '\u25BC'; + const label = document.createElement('span'); + label.textContent = 'Functions'; + header.appendChild(arrow); + header.appendChild(label); + + header.addEventListener('click', (e) => { + e.stopPropagation(); + const collapsed = section.classList.toggle('ctx-menu-section--collapsed'); + arrow.textContent = collapsed ? '\u25B6' : '\u25BC'; + }); + + const body = document.createElement('div'); + body.className = 'ctx-menu-section-body'; + + for (const ft of funcTabs) { + const saved = ft.saved; + const item = document.createElement('div'); + item.className = 'context-menu-items'; + item.textContent = `Call ${saved.name}`; + item.dataset.functionTabId = ft.id; + item.style.borderLeftColor = callColor; + item.style.color = callColor; + + item.addEventListener('click', () => { + const stage = tabManager.getStage(); + const activeLayer = tabManager.getActiveLayer(); + const rect = item.getBoundingClientRect(); + const containerRect = stage.container().getBoundingClientRect(); + const x = rect.x - containerRect.x; + const y = rect.y - containerRect.y; + Nodes.CreateCallNode(saved.name, saved.inputParams, saved.outputParams, + { x, y }, activeLayer, stage, saved.docString || ''); + activeLayer.draw(); + document.getElementById('ctx-menu-container').classList.add('hidden'); + }); + + body.appendChild(item); + } + + section.appendChild(header); + section.appendChild(body); + contextMenu.appendChild(section); +} diff --git a/src/js/ui/variableList.js b/src/js/ui/variableList.js new file mode 100644 index 0000000..dcf4cb6 --- /dev/null +++ b/src/js/ui/variableList.js @@ -0,0 +1,527 @@ +import { colorMap, lightenHex } from '../core/colorMap.js' +import { ContextMenu } from '../editor/contextMenu.js' +import { deleteNodeByGroup, deleteWire } from '../editor/deleteHandler.js' +import { applyOrphanOverlay } from '../editor/orphanOverlay.js' +import { applyMismatchToWire, removeMismatchFromWire } from '../utils/wireMismatch.js' +import { showAlert, showConfirm } from './dialogs.js' +import { tabManager } from '../editor/tabManager.js' + +class VariableList { + + constructor() { + this.variables = []; + this.variablesElements = []; + this.layer = null; + this.stage = null; + } + + init(layer, stage) { + this.layer = layer; + this.stage = stage; + } + + getNodesForVariable(name) { + if (!this.layer) return []; + return this.layer.children.filter(grp => + grp.customClass?.type?.isGetSet && + grp.customClass.type.typeOfNode.slice(4) === name + ); + } + + getWiresForNode(node) { + const wires = []; + for (const pin of node.customClass.inputPins) { + if (pin.wire) wires.push(pin.wire); + } + for (const pin of node.customClass.outputPins) { + if (Array.isArray(pin.wire)) wires.push(...pin.wire.filter(Boolean)); + } + return wires; + } + + makeContextMenuItem(variable, setOrGet) { + let div = document.createElement("div"); + div.classList.toggle("context-menu-items", true); + div.setAttribute('data-datatype', `${variable.dataType}`); + div.innerHTML = `${(setOrGet == 'set') ? 'Set' : 'Get'} ${variable.name}`; + const variablesColor = colorMap['Get'] || colorMap['Text']; + div.style.borderLeftColor = variablesColor; + div.style.color = lightenHex(variablesColor); + return div; + } + + addVariable(variable) { + this.variables.push(variable); + let el = this.makeLeftPanelVariableListItem(variable); + document.getElementById("variable-list").appendChild(el); + let set = this.makeContextMenuItem(variable, 'set'); + let get = this.makeContextMenuItem(variable, 'get'); + const variablesBody = document.getElementById("context-menu-variables-body"); + const container = variablesBody || document.getElementById("context-menu"); + container.appendChild(get); + container.appendChild(set); + ContextMenu.addEventToCtxMenuItems(set); + ContextMenu.addEventToCtxMenuItems(get); + this.variablesElements.push({ name: variable.name, type: 'get', el: get }); + this.variablesElements.push({ name: variable.name, type: 'set', el: set }); + } + + makeLeftPanelVariableListItem(variable) { + let li = document.createElement('li'); + li.id = `${variable.dataType}-${variable.name}`; + li.classList.toggle('left-panel-variable', true); + li.style.borderWidth = '2px'; + li.style.borderStyle = 'solid'; + li.style.boxShadow = `inset 0px 0px 5px ${colorMap[variable.dataType]}`; + li.style.backgroundColor = 'transparent'; + li.style.borderColor = `${colorMap[variable.dataType]}`; + li.setAttribute("draggable", "true"); + + let nameSpan = document.createElement('span'); + nameSpan.classList.add('var-name-text'); + nameSpan.textContent = variable.name; + nameSpan.style.overflow = 'hidden'; + nameSpan.style.textOverflow = 'ellipsis'; + nameSpan.style.whiteSpace = 'nowrap'; + nameSpan.style.flex = '1'; + li.appendChild(nameSpan); + + let actionsDiv = document.createElement('div'); + actionsDiv.classList.add('var-actions'); + + let editBtn = document.createElement('button'); + editBtn.innerHTML = ''; + editBtn.title = 'Edit variable'; + editBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._showInlineEditForm(li, variable); + }); + + let deleteBtn = document.createElement('button'); + deleteBtn.classList.add('var-delete-btn'); + deleteBtn.innerHTML = ''; + deleteBtn.title = 'Delete variable'; + deleteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._confirmAndDelete(variable.name); + }); + + actionsDiv.appendChild(editBtn); + actionsDiv.appendChild(deleteBtn); + li.appendChild(actionsDiv); + + li.addEventListener('mouseover', () => { + li.style.boxShadow = `inset 0px 0px 30px ${colorMap[variable.dataType]}`; + }); + li.addEventListener('mouseleave', () => { + li.style.boxShadow = `inset 0px 0px 5px ${colorMap[variable.dataType]}`; + }); + li.addEventListener("dragstart", (e) => { + e.dataTransfer.setData("variableName", `${variable.name}`); + e.dataTransfer.setData("dataType", `${variable.dataType}`); + }); + return li; + } + + // ---- DELETE FLOW ---- + + _confirmAndDelete(name) { + const nodes = this.getNodesForVariable(name); + const count = nodes.length; + let msg; + if (count > 0) { + msg = `Variable '${name}' is used by ${count} node(s) on the canvas. These nodes will be marked as orphaned. Delete anyway?`; + } else { + msg = `Delete variable '${name}'?`; + } + showConfirm(msg, () => { + this.deleteVariable(name); + }); + } + + deleteVariable(name) { + const nodes = this.getNodesForVariable(name); + + const idx = this.variables.findIndex(v => v.name === name); + if (idx !== -1) this.variables.splice(idx, 1); + + const li = document.querySelector(`#variable-list .left-panel-variable[id$="-${name}"]`); + if (li) li.remove(); + + this.variablesElements = this.variablesElements.filter(item => { + if (item.name === name) { + item.el.remove(); + return false; + } + return true; + }); + + for (const grp of nodes) { + this._applyOrphanOverlay(grp); + } + + if (this.stage) this.stage.draw(); + } + + _applyOrphanOverlay(grp) { + applyOrphanOverlay(grp, this.layer, this.stage, 'Deleted Var'); + } + + // ---- EDIT FLOW ---- + + _showInlineEditForm(li, variable) { + const originalContent = li.innerHTML; + const originalDraggable = li.getAttribute('draggable'); + li.setAttribute('draggable', 'false'); + li.innerHTML = ''; + li.style.flexDirection = 'column'; + li.style.alignItems = 'stretch'; + + const form = document.createElement('div'); + form.classList.add('var-inline-edit'); + + const nameInput = document.createElement('input'); + nameInput.type = 'text'; + nameInput.value = variable.name; + nameInput.placeholder = 'Name'; + + const typeSelect = document.createElement('select'); + for (const t of ['Number', 'Boolean', 'String', 'Array']) { + const opt = document.createElement('option'); + opt.value = t; + opt.textContent = t; + if (t === variable.dataType) opt.selected = true; + typeSelect.appendChild(opt); + } + + const valueInput = this._createValueInput(variable.dataType, variable.value); + + typeSelect.addEventListener('change', () => { + const newValInput = this._createValueInput(typeSelect.value, null); + form.replaceChild(newValInput, form.children[2]); + }); + + const actionsDiv = document.createElement('div'); + actionsDiv.classList.add('var-edit-actions'); + + const saveBtn = document.createElement('button'); + saveBtn.classList.add('var-edit-save'); + saveBtn.textContent = 'Save'; + saveBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const newName = nameInput.value.trim(); + const newType = typeSelect.value; + const valEl = form.children[2]; + const newValue = this._extractValue(newType, valEl); + + if (!this._validateEdit(variable.name, newName, newType, newValue)) return; + + this.editVariable(variable.name, { + name: newName, + dataType: newType, + value: newValue, + }); + + variable.name = newName; + variable.dataType = newType; + variable.value = newValue; + + this._restoreListItem(li, variable); + }); + + const cancelBtn = document.createElement('button'); + cancelBtn.classList.add('var-edit-cancel'); + cancelBtn.textContent = 'Cancel'; + cancelBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._restoreListItem(li, variable); + }); + + actionsDiv.appendChild(saveBtn); + actionsDiv.appendChild(cancelBtn); + + form.appendChild(nameInput); + form.appendChild(typeSelect); + form.appendChild(valueInput); + form.appendChild(actionsDiv); + li.appendChild(form); + } + + _createValueInput(dataType, currentValue) { + if (dataType === 'Boolean') { + const sel = document.createElement('select'); + const optT = document.createElement('option'); + optT.value = 'true'; optT.textContent = 'True'; + const optF = document.createElement('option'); + optF.value = 'false'; optF.textContent = 'False'; + sel.appendChild(optT); + sel.appendChild(optF); + if (currentValue === 'false') optF.selected = true; + return sel; + } + const inp = document.createElement('input'); + if (dataType === 'Number') { + inp.type = 'number'; + inp.value = currentValue != null ? currentValue : 0; + inp.placeholder = 'Default value'; + } else if (dataType === 'String') { + inp.type = 'text'; + let display = currentValue || ''; + if (display.startsWith("'") && display.endsWith("'")) display = display.slice(1, -1); + inp.value = display; + inp.placeholder = 'Default value'; + } else if (dataType === 'Array') { + inp.type = 'text'; + inp.value = currentValue || '[]'; + inp.placeholder = '[1, 2, 3]'; + } + return inp; + } + + _extractValue(dataType, el) { + const raw = el.value; + if (dataType === 'Boolean') return raw; + if (dataType === 'Number') return raw.toString(); + if (dataType === 'String') return `'${raw}'`; + if (dataType === 'Array') return raw; + return raw; + } + + _validateEdit(oldName, newName, newType, newValue) { + if (newName.length === 0) { + showAlert("Variable Name Can't be empty!"); + return false; + } + if (newName.includes(' ')) { + showAlert("Variable Name Can't Have Spaces"); + return false; + } + if (/^[0-9]/.test(newName)) { + showAlert("Variable Name should start with an alphabet or '_'"); + return false; + } + if (newName !== oldName && this.variables.some(v => v.name === newName)) { + showAlert("Variable Already Exists"); + return false; + } + if (newType === 'Number' && newValue.length === 0) { + showAlert("Empty/Invalid Input"); + return false; + } + if (newType === 'String' && newValue === "''") { + showAlert("Empty/Invalid Input"); + return false; + } + if (newType === 'Array') { + if (newValue.length === 0 || newValue[0] !== '[' || newValue[newValue.length - 1] !== ']') { + showAlert("Invalid Array format"); + return false; + } + } + return true; + } + + _restoreListItem(li, variable) { + li.innerHTML = ''; + li.style.flexDirection = ''; + li.style.alignItems = ''; + li.setAttribute('draggable', 'true'); + li.id = `${variable.dataType}-${variable.name}`; + li.style.boxShadow = `inset 0px 0px 5px ${colorMap[variable.dataType]}`; + li.style.borderColor = `${colorMap[variable.dataType]}`; + + let nameSpan = document.createElement('span'); + nameSpan.classList.add('var-name-text'); + nameSpan.textContent = variable.name; + nameSpan.style.overflow = 'hidden'; + nameSpan.style.textOverflow = 'ellipsis'; + nameSpan.style.whiteSpace = 'nowrap'; + nameSpan.style.flex = '1'; + li.appendChild(nameSpan); + + let actionsDiv = document.createElement('div'); + actionsDiv.classList.add('var-actions'); + + let editBtn = document.createElement('button'); + editBtn.innerHTML = ''; + editBtn.title = 'Edit variable'; + editBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._showInlineEditForm(li, variable); + }); + + let deleteBtn = document.createElement('button'); + deleteBtn.classList.add('var-delete-btn'); + deleteBtn.innerHTML = ''; + deleteBtn.title = 'Delete variable'; + deleteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._confirmAndDelete(variable.name); + }); + + actionsDiv.appendChild(editBtn); + actionsDiv.appendChild(deleteBtn); + li.appendChild(actionsDiv); + + li.addEventListener('mouseover', () => { + li.style.boxShadow = `inset 0px 0px 30px ${colorMap[variable.dataType]}`; + }); + li.addEventListener('mouseleave', () => { + li.style.boxShadow = `inset 0px 0px 5px ${colorMap[variable.dataType]}`; + }); + li.addEventListener("dragstart", (e) => { + e.dataTransfer.setData("variableName", variable.name); + e.dataTransfer.setData("dataType", variable.dataType); + }); + } + + editVariable(oldName, newData) { + const oldVar = this.variables.find(v => v.name === oldName); + if (!oldVar) return; + const oldType = oldVar.dataType; + const typeChanged = oldType !== newData.dataType; + const nameChanged = oldName !== newData.name; + + oldVar.name = newData.name; + oldVar.dataType = newData.dataType; + oldVar.value = newData.value; + + this.variablesElements.forEach(item => { + if (item.name === oldName) { + item.name = newData.name; + const prefix = item.type === 'get' ? 'Get' : 'Set'; + item.el.innerHTML = `${prefix} ${newData.name}`; + item.el.setAttribute('data-datatype', newData.dataType); + } + }); + + const nodes = this.getNodesForVariable(oldName); + for (const grp of nodes) { + const cc = grp.customClass; + const prefix = cc.type.typeOfNode.slice(0, 3); + cc.type.typeOfNode = `${prefix} ${newData.name}`; + cc.nodeDescription.nodeTitle = cc.type.typeOfNode; + + if (cc.titleText) { + cc.titleText.text(cc.type.typeOfNode); + } + + if (typeChanged) { + if (cc.nodeDescription.inputs) { + for (const key of Object.keys(cc.nodeDescription.inputs)) { + cc.nodeDescription.inputs[key].dataType = newData.dataType; + } + } + if (cc.nodeDescription.outputs) { + for (const key of Object.keys(cc.nodeDescription.outputs)) { + cc.nodeDescription.outputs[key].dataType = newData.dataType; + } + } + + if (cc.titleBg) { + cc.titleBg.fill(colorMap[newData.dataType]); + } + + const newColor = colorMap[newData.dataType]; + const updatePin = (p) => { + p.stroke(newColor); + p.attrs.pinDataType = newData.dataType; + if (p.fill() && p.fill() !== '' && p.fill() !== 'transparent') { + p.fill(newColor); + } + p.off('wireconnected'); + p.on('wireconnected', () => { p.fill(newColor); }); + }; + + for (const pin of cc.inputPins) { + if (pin.thisNode) updatePin(pin.thisNode); + } + for (const pin of cc.outputPins) { + grp.find('.pin').filter(p => + p.attrs.pinType === 'outp' && p.attrs.helper?.startsWith('out-') + ).forEach(updatePin); + } + + this._markMismatchedWires(grp, newData.dataType); + } + } + + if (this.layer) this.layer.draw(); + if (this.stage) { + const wireLayer = tabManager.getActiveWireLayer(); + if (wireLayer) wireLayer.draw(); + } + } + + // ---- TYPE-MISMATCH WIRE FLOW ---- + + _markMismatchedWires(grp, newDataType) { + const cc = grp.customClass; + const wireLayer = this.stage ? tabManager.getActiveWireLayer() : null; + if (!wireLayer) return; + + for (const pin of cc.inputPins) { + if (pin.wire) { + const srcPin = pin.wire.attrs.src; + const srcType = srcPin && srcPin.attrs.pinDataType; + if (srcType && srcType !== 'Data' && newDataType !== 'Data' && srcType !== newDataType) { + applyMismatchToWire(pin.wire, wireLayer, this.stage); + } else { + removeMismatchFromWire(pin.wire); + } + } + } + + for (const pin of cc.outputPins) { + if (Array.isArray(pin.wire)) { + for (const w of pin.wire) { + if (!w) continue; + const destPin = w.attrs.dest; + const destType = destPin && destPin.attrs.pinDataType; + if (destType && destType !== 'Data' && newDataType !== 'Data' && destType !== newDataType) { + applyMismatchToWire(w, wireLayer, this.stage); + } else { + removeMismatchFromWire(w); + } + } + } + } + } + + deleteAllVariables() { + this.variables.length = 0; + document.getElementById("variable-list").innerHTML = ''; + this.variablesElements.forEach((item) => { + item.el.remove(); + }); + this.variablesElements = []; + } + + switchToTab(tabVariables) { + document.getElementById("variable-list").innerHTML = ''; + this.variablesElements.forEach((item) => { + item.el.remove(); + }); + this.variablesElements = []; + + this.variables = tabVariables; + + for (const variable of this.variables) { + const el = this.makeLeftPanelVariableListItem(variable); + document.getElementById("variable-list").appendChild(el); + + const set = this.makeContextMenuItem(variable, 'set'); + const get = this.makeContextMenuItem(variable, 'get'); + const variablesBody = document.getElementById("context-menu-variables-body"); + const container = variablesBody || document.getElementById("context-menu"); + container.appendChild(get); + container.appendChild(set); + ContextMenu.addEventToCtxMenuItems(set); + ContextMenu.addEventToCtxMenuItems(get); + this.variablesElements.push({ name: variable.name, type: 'get', el: get }); + this.variablesElements.push({ name: variable.name, type: 'set', el: set }); + } + } +} + +export var variableList = new VariableList(); diff --git a/javascript/LeftPanel/LeftPanel.js b/src/js/ui/variablePanel.js similarity index 95% rename from javascript/LeftPanel/LeftPanel.js rename to src/js/ui/variablePanel.js index 99c856b..c974886 100644 --- a/javascript/LeftPanel/LeftPanel.js +++ b/src/js/ui/variablePanel.js @@ -1,6 +1,6 @@ -import { colorMap } from '../ColorMap/colorMap.js' -import { variableList } from '../Variable/variable.js' -import { showAlert } from '../main/alertBox.js' +import { colorMap } from '../core/colorMap.js' +import { variableList } from './variableList.js' +import { showAlert } from './dialogs.js' export class leftPanel { constructor() { let isBooleanValid = false; @@ -25,10 +25,10 @@ export class leftPanel { }); let createVariableForm = document.getElementById("create-variables"); let forms = { - numberForm: document.getElementById("number-default-form"), - stringForm: document.getElementById("string-default-form"), - boolForm: document.getElementById("bool-default-form"), - arrayForm: document.getElementById("array-default-form"), + numberForm: document.getElementById("number-default-value"), + stringForm: document.getElementById("string-default-value"), + boolForm: document.getElementById("bool-default-value"), + arrayForm: document.getElementById("array-default-value"), } let formInputsField = { numberFormField: document.getElementById("number-default-value"), diff --git a/src/js/utils/docTooltip.js b/src/js/utils/docTooltip.js new file mode 100644 index 0000000..1338cef --- /dev/null +++ b/src/js/utils/docTooltip.js @@ -0,0 +1,46 @@ +/** + * Doc tooltip for DOM elements (e.g. function list info icon). + * Uses the same CSS class as call-node-doc-tooltip. + */ +let _el = null; +let _owner = null; + +function getOrCreateEl() { + if (_el) return _el; + const el = document.createElement('div'); + el.className = 'call-node-doc-tooltip'; + el.setAttribute('aria-hidden', 'true'); + document.body.appendChild(el); + _el = el; + return el; +} + +export function showDocTooltip(anchorEl, docString) { + if (!docString || !anchorEl) return; + const el = getOrCreateEl(); + _owner = anchorEl; + el.textContent = docString; + el.style.display = 'block'; + el.style.visibility = 'hidden'; + + const rect = anchorEl.getBoundingClientRect(); + const tw = el.offsetWidth; + const th = el.offsetHeight; + const gap = 8; + let left = rect.left + (rect.width / 2) - (tw / 2); + let top = rect.top - th - gap; + left = Math.max(6, Math.min(left, document.documentElement.clientWidth - tw - 6)); + top = Math.max(6, Math.min(top, document.documentElement.clientHeight - th - 6)); + el.style.left = Math.round(left) + 'px'; + el.style.top = Math.round(top) + 'px'; + el.style.visibility = 'visible'; +} + +export function hideDocTooltip(anchorEl) { + if (_owner !== anchorEl) return; + _owner = null; + if (_el) { + _el.style.display = 'none'; + _el.textContent = ''; + } +} diff --git a/src/js/utils/wireMismatch.js b/src/js/utils/wireMismatch.js new file mode 100644 index 0000000..d5f0ac3 --- /dev/null +++ b/src/js/utils/wireMismatch.js @@ -0,0 +1,127 @@ +import { deleteWire } from '../editor/deleteHandler.js'; +import { tabManager } from '../editor/tabManager.js'; + +/** + * Check if two data types are compatible (no mismatch). + * Data type is universal; otherwise types must match. + */ +export function isTypeCompatible(srcType, destType) { + if (!srcType || !destType) return true; + if (srcType === 'Data' || destType === 'Data') return true; + return srcType === destType; +} + +/** + * Apply dashed line and type-mismatch indicator to a wire. + * @param {Konva.Line} wire + * @param {Konva.Layer} wireLayer + * @param {Konva.Stage} [stage] - for draw() after delete + */ +export function applyMismatchToWire(wire, wireLayer, stage) { + if (wire.isMismatched) return; + wire.isMismatched = true; + wire.dash([10, 5]); + + const r = 8; + const indicatorGrp = new Konva.Group({ x: 0, y: 0 }); + + const bg = new Konva.Circle({ + radius: r, + fill: 'rgba(50, 20, 0, 0.9)', + stroke: '#ff8800', + strokeWidth: 1.5, + }); + indicatorGrp.add(bg); + + const cross1 = new Konva.Line({ + points: [-4, -4, 4, 4], + stroke: '#fff', + strokeWidth: 2, + lineCap: 'round', + }); + const cross2 = new Konva.Line({ + points: [-4, 4, 4, -4], + stroke: '#fff', + strokeWidth: 2, + lineCap: 'round', + }); + indicatorGrp.add(cross1); + indicatorGrp.add(cross2); + + const tooltip = new Konva.Label({ x: r + 4, y: -10, visible: false }); + tooltip.add(new Konva.Tag({ + fill: 'rgba(30, 10, 0, 0.9)', + cornerRadius: 3, + stroke: '#ff8800', + strokeWidth: 0.5, + })); + tooltip.add(new Konva.Text({ + text: 'Type Mismatch', + fontSize: 10, + fontFamily: 'Verdana', + fill: '#ffaa44', + padding: 4, + })); + indicatorGrp.add(tooltip); + + indicatorGrp.on('mouseenter', () => { + bg.fill('#ff4400'); + cross1.stroke('#fff'); + cross2.stroke('#fff'); + tooltip.visible(true); + document.body.style.cursor = 'pointer'; + wireLayer.draw(); + }); + indicatorGrp.on('mouseleave', () => { + bg.fill('rgba(50, 20, 0, 0.9)'); + tooltip.visible(false); + document.body.style.cursor = 'default'; + wireLayer.draw(); + }); + indicatorGrp.on('click', (e) => { + e.cancelBubble = true; + document.body.style.cursor = 'default'; + indicatorGrp.destroy(); + deleteWire(wire); + const s = stage || (wireLayer && wireLayer.getStage()) || tabManager.getStage(); + if (s) s.draw(); + }); + + const updatePosition = () => { + const pts = wire.points(); + if (pts.length >= 4) { + indicatorGrp.x((pts[0] + pts[pts.length - 2]) / 2); + indicatorGrp.y((pts[1] + pts[pts.length - 1]) / 2); + } + }; + updatePosition(); + + const srcNode = wire.attrs.src?.getParent(); + const destNode = wire.attrs.dest?.getParent(); + if (srcNode) srcNode.on('dragmove.mismatch', updatePosition); + if (destNode) destNode.on('dragmove.mismatch', updatePosition); + wire._mismatchDragCleanup = () => { + if (srcNode) srcNode.off('dragmove.mismatch'); + if (destNode) destNode.off('dragmove.mismatch'); + }; + + wireLayer.add(indicatorGrp); + wire._mismatchIndicator = indicatorGrp; +} + +/** + * Remove mismatch styling from a wire. + */ +export function removeMismatchFromWire(wire) { + if (!wire.isMismatched) return; + wire.isMismatched = false; + wire.dash([]); + if (wire._mismatchDragCleanup) { + wire._mismatchDragCleanup(); + wire._mismatchDragCleanup = null; + } + if (wire._mismatchIndicator) { + wire._mismatchIndicator.destroy(); + wire._mismatchIndicator = null; + } +} diff --git a/src/public/assets/starter.json b/src/public/assets/starter.json new file mode 100644 index 0000000..8849bf6 --- /dev/null +++ b/src/public/assets/starter.json @@ -0,0 +1 @@ +{"variables":[{"name":"quantity","dataType":"Number","value":"100"},{"name":"unit_price","dataType":"Number","value":"1000"}],"nodesData":[{"position":{"x":329.66989447061565,"y":220.91332290742616},"nodeDescription":{"nodeTitle":"Begin","color":"Begin","rows":2,"colums":10,"execIn":false,"pinExecInId":null,"execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"10","outOrder":0}}}},{"position":{"x":297.7233644556677,"y":381.88250599193964},"nodeDescription":{"nodeTitle":"Get quantity","outputs":{"output0":{"outputTitle":"Value(Ref)","dataType":"Number","pinOutId":"409","outOrder":0}},"color":"Get","rows":2,"colums":10}},{"position":{"x":481.099434396144,"y":410.6046845633682},"nodeDescription":{"nodeTitle":"Multiply","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Number","defValue":0,"pinInId":"419","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Number","defValue":0,"pinInId":"424","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"429","outOrder":0}}}},{"position":{"x":288.7184820151915,"y":508.8933750395587},"nodeDescription":{"nodeTitle":"Get unit_price","outputs":{"output0":{"outputTitle":"Value(Ref)","dataType":"Number","pinOutId":"444","outOrder":0}},"color":"Get","rows":2,"colums":10}},{"position":{"x":1084.7283004870192,"y":258.0682468154959},"nodeDescription":{"nodeTitle":"Call display_dollar_price","execIn":true,"pinExecInId":"475","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"476","outOrder":0}},"color":"Call","rows":2,"colums":14,"isCallFunction":true,"calledFunctionName":"display_dollar_price","docString":"Function to fetch USD exchange rate and do the INR to USD conversion","inputs":{"input0":{"inputTitle":"inr_amount","dataType":"Number","defValue":"0","pinInId":"477","isInputBoxRequired":true}}}},{"position":{"x":705.7092310126123,"y":164.6421543315391},"nodeDescription":{"nodeTitle":"Call get_discounted_price","execIn":true,"pinExecInId":"540","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"541","outOrder":0}},"color":"Call","rows":3,"colums":14,"isCallFunction":true,"calledFunctionName":"get_discounted_price","docString":"Function to get the discounted prices.","inputs":{"input0":{"inputTitle":"total_amount","dataType":"Number","defValue":"0","pinInId":"542","isInputBoxRequired":true},"input1":{"inputTitle":"discount_perc","dataType":"Number","defValue":"0","pinInId":"547","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"discounted_amount","dataType":"Number","pinOutId":"552","outOrder":1}}}},{"position":{"x":1784.8503547906926,"y":255.8692215133575},"nodeDescription":{"nodeTitle":"Print","color":"Print","rows":3,"colums":12,"execIn":true,"pinExecInId":"1122","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"1123","outOrder":0}},"inputs":{"input0":{"inputTitle":"Value","dataType":"Data","defValue":"'hello'","pinInId":"1124","isInputBoxRequired":true}}}},{"position":{"x":1447.643880113113,"y":264.1809681973247},"nodeDescription":{"nodeTitle":"Call recursive_factorial","execIn":true,"pinExecInId":"1100","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"1101","outOrder":0}},"color":"Call","rows":2,"colums":14,"isCallFunction":true,"calledFunctionName":"recursive_factorial","docString":"","inputs":{"input0":{"inputTitle":"num","dataType":"Number","defValue":"10","pinInId":"1102","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"factorial","dataType":"Number","pinOutId":"1107","outOrder":1}}}}],"wireData":[{"srcId":"444","destId":"424"},{"srcId":"409","destId":"419"},{"srcId":"10","destId":"540"},{"srcId":"541","destId":"475"},{"srcId":"429","destId":"542"},{"srcId":"552","destId":"477"},{"srcId":"476","destId":"1100"},{"srcId":"1101","destId":"1122"},{"srcId":"1107","destId":"1124"}],"groupsData":[{"position":{"x":1398.019131518612,"y":137.39846411260328},"width":625.3779656250006,"height":381.60818718750033,"name":"Calling a recursive function for test"}],"functions":[{"name":"get_discounted_price","inputParams":[{"name":"total_amount","dataType":"Number","defValue":"0"},{"name":"discount_perc","dataType":"Number","defValue":"0"}],"outputParams":[{"name":"discounted_amount","dataType":"Number"}],"variables":[],"docString":"Function to get the discounted prices.","nodesData":[{"position":{"x":369.2488819263885,"y":218.72366699514882},"nodeDescription":{"nodeTitle":"FunctionBegin","execIn":false,"pinExecInId":null,"execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"38","outOrder":0}},"color":"FunctionBegin","rows":3,"colums":12,"isDeletable":false,"outputs":{"output0":{"outputTitle":"total_amount","dataType":"Number","pinOutId":"39","outOrder":1},"output1":{"outputTitle":"discount_perc","dataType":"Number","pinOutId":"41","outOrder":2}}}},{"position":{"x":858.8968414596872,"y":322.2400151803591},"nodeDescription":{"nodeTitle":"Multiply","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Number","defValue":0,"pinInId":"51","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Number","defValue":0,"pinInId":"56","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"61","outOrder":0}}}},{"position":{"x":787.2415126615012,"y":495.4826455658467},"nodeDescription":{"nodeTitle":"Divide","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Number","defValue":0,"pinInId":"76","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Number","defValue":"100","pinInId":"81","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"86","outOrder":0}}}},{"position":{"x":563.2052314823627,"y":446.50305372911185},"nodeDescription":{"nodeTitle":"Subtract","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Number","defValue":"100","pinInId":"97","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Number","defValue":0,"pinInId":"102","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"107","outOrder":0}}}},{"position":{"x":1078.0120126333236,"y":196.95495951215565},"nodeDescription":{"nodeTitle":"Return","execIn":true,"pinExecInId":"129","color":"Return","rows":2,"colums":12,"isDeletable":false,"inputs":{"input0":{"inputTitle":"discounted_amount","dataType":"Number","defValue":null,"pinInId":"130","isInputBoxRequired":false}}}}],"wireData":[{"srcId":"38","destId":"129"},{"srcId":"61","destId":"130"},{"srcId":"86","destId":"56"},{"srcId":"107","destId":"76"},{"srcId":"41","destId":"102"},{"srcId":"39","destId":"51"}],"groupsData":[]},{"name":"display_dollar_price","inputParams":[{"name":"inr_amount","dataType":"Number","defValue":"0"}],"outputParams":[],"variables":[],"docString":"Function to fetch USD exchange rate and do the INR to USD conversion","nodesData":[{"position":{"x":200.40309411510998,"y":116.32079483144673},"nodeDescription":{"nodeTitle":"FunctionBegin","execIn":false,"pinExecInId":null,"execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"161","outOrder":0}},"color":"FunctionBegin","rows":2,"colums":12,"isDeletable":false,"outputs":{"output0":{"outputTitle":"inr_amount","dataType":"Number","pinOutId":"162","outOrder":1}}}},{"position":{"x":630.439618606527,"y":595.1399853189833},"nodeDescription":{"nodeTitle":"HttpRequest","color":"Func","rows":2,"colums":12,"execIn":true,"pinExecInId":"177","execOut":{"execOut0":{"execOutTitle":"OnSuccess","pinExecOutId":"178","outOrder":0},"execOut1":{"execOutTitle":"OnFail","pinExecOutId":"180","outOrder":2},"execOut2":{"execOutTitle":"Continue","pinExecOutId":"182","outOrder":3}},"inputs":{"input0":{"inputTitle":"URL","dataType":"String","defValue":"'https://api.frankfurter.app/latest?from=USD&to=INR'","pinInId":"184","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"JSON","dataType":"Data","pinOutId":"189","outOrder":1}}}},{"position":{"x":922.9646427992587,"y":749.5765375036962},"nodeDescription":{"nodeTitle":"GetByName(JSON)","color":"Obj","rows":2,"colums":12,"inputs":{"input0":{"inputTitle":"JSON","dataType":"Data","defValue":null,"pinInId":"230","isInputBoxRequired":false},"input1":{"inputTitle":"Name","dataType":"String","defValue":"'rates'","pinInId":"232","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Data","dataType":"Data","pinOutId":"237","outOrder":0}}}},{"position":{"x":1173.4523588393488,"y":734.2697894033207},"nodeDescription":{"nodeTitle":"GetByName(JSON)","color":"Obj","rows":2,"colums":12,"inputs":{"input0":{"inputTitle":"JSON","dataType":"Data","defValue":null,"pinInId":"252","isInputBoxRequired":false},"input1":{"inputTitle":"Name","dataType":"String","defValue":"'INR'","pinInId":"254","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Data","dataType":"Data","pinOutId":"259","outOrder":0}}}},{"position":{"x":1427.9573136257832,"y":746.651492565948},"nodeDescription":{"nodeTitle":"ParseFloat","color":"Str","rows":2,"colums":11,"inputs":{"input0":{"inputTitle":"String","dataType":"String","defValue":"'0.0'","pinInId":"275","isInputBoxRequired":false}},"outputs":{"output0":{"outputTitle":"Number","dataType":"Number","pinOutId":"277","outOrder":0}}}},{"position":{"x":1525.9905834056824,"y":145.19684752858797},"nodeDescription":{"nodeTitle":"Divide","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Number","defValue":0,"pinInId":"297","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Number","defValue":0,"pinInId":"302","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"307","outOrder":0}}}},{"position":{"x":1750.1563808724957,"y":203.03720934837258},"nodeDescription":{"nodeTitle":"Round","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"Value","dataType":"Number","defValue":0,"pinInId":"469","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"474","outOrder":0}}}},{"position":{"x":1980.7732783708489,"y":195.18502976456716},"nodeDescription":{"nodeTitle":"ToString","color":"Str","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"Value","dataType":"Data","defValue":0,"pinInId":"510","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"String","pinOutId":"515","outOrder":0}}}},{"position":{"x":2250.8344686723267,"y":211.72541952720098},"nodeDescription":{"nodeTitle":"Concat","color":"Str","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"A","dataType":"String","defValue":"''","pinInId":"490","isInputBoxRequired":true},"input1":{"inputTitle":"B","dataType":"String","defValue":"' USD'","pinInId":"495","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"String","pinOutId":"500","outOrder":0}}}},{"position":{"x":2849.4639972846294,"y":368.03953046698064},"nodeDescription":{"nodeTitle":"Return","execIn":true,"pinExecInId":"155","color":"Return","rows":2,"colums":12,"isDeletable":false}},{"position":{"x":2486.452960358817,"y":392.9088963218603},"nodeDescription":{"nodeTitle":"Print","color":"Print","rows":3,"colums":12,"execIn":true,"pinExecInId":"205","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"206","outOrder":0}},"inputs":{"input0":{"inputTitle":"Value","dataType":"Data","defValue":"'hello'","pinInId":"207","isInputBoxRequired":true}}}}],"wireData":[{"srcId":"161","destId":"177"},{"srcId":"189","destId":"230"},{"srcId":"237","destId":"252"},{"srcId":"259","destId":"275"},{"srcId":"162","destId":"297"},{"srcId":"277","destId":"302"},{"srcId":"307","destId":"469"},{"srcId":"474","destId":"510"},{"srcId":"515","destId":"490"},{"srcId":"178","destId":"205"},{"srcId":"206","destId":"155"},{"srcId":"500","destId":"207"}],"groupsData":[{"position":{"x":460.312324873067,"y":525.8005840089143},"width":1239.5888109776342,"height":428.3992868424674,"name":"Get Data From the API"}]},{"name":"recursive_factorial","inputParams":[{"name":"num","dataType":"Number","defValue":"10"}],"outputParams":[{"name":"factorial","dataType":"Number"}],"variables":[{"name":"ret","dataType":"Number","value":"1"}],"docString":"","nodesData":[{"position":{"x":1023.0066749449601,"y":223.81688341778772},"nodeDescription":{"nodeTitle":"Subtract","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Number","defValue":0,"pinInId":"702","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Number","defValue":"1","pinInId":"707","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"712","outOrder":0}}}},{"position":{"x":1298.3243883332486,"y":134.7520340063008},"nodeDescription":{"nodeTitle":"Call recursive_factorial","execIn":true,"pinExecInId":"904","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"905","outOrder":0}},"color":"Call","rows":2,"colums":14,"isCallFunction":true,"calledFunctionName":"recursive_factorial","docString":"","inputs":{"input0":{"inputTitle":"num","dataType":"Number","defValue":"10","pinInId":"906","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"factorial","dataType":"Number","pinOutId":"911","outOrder":1}}}},{"position":{"x":1699.9591584998007,"y":182.40218599576008},"nodeDescription":{"nodeTitle":"Set ret","execIn":true,"pinExecInId":"1019","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"1020","outOrder":0}},"inputs":{"input0":{"inputTitle":"Value","dataType":"Number","defValue":0,"pinInId":"1021"}},"outputs":{"output0":{"outputTitle":"Value(Ref)","dataType":"Number","pinOutId":"1026","outOrder":1}},"color":"Func","rows":2,"colums":12}},{"position":{"x":1928.7081075436731,"y":-55.477309898947595},"nodeDescription":{"nodeTitle":"Return","execIn":true,"pinExecInId":"523","color":"Return","rows":2,"colums":12,"isDeletable":false,"inputs":{"input0":{"inputTitle":"factorial","dataType":"Number","defValue":null,"pinInId":"524","isInputBoxRequired":false}}}},{"position":{"x":1574.750390698565,"y":-150.03088000044787},"nodeDescription":{"nodeTitle":"Set ret","execIn":true,"pinExecInId":"1052","execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"1053","outOrder":0}},"inputs":{"input0":{"inputTitle":"Value","dataType":"Number","defValue":"1","pinInId":"1054"}},"outputs":{"output0":{"outputTitle":"Value(Ref)","dataType":"Number","pinOutId":"1059","outOrder":1}},"color":"Func","rows":2,"colums":12}},{"position":{"x":1584.2318667750133,"y":72.17595609453402},"nodeDescription":{"nodeTitle":"Get ret","outputs":{"output0":{"outputTitle":"Value(Ref)","dataType":"Number","pinOutId":"1080","outOrder":0}},"color":"Get","rows":2,"colums":10}},{"position":{"x":52.12485457043243,"y":78.97408515991168},"nodeDescription":{"nodeTitle":"FunctionBegin","execIn":false,"pinExecInId":null,"execOut":{"execOut0":{"execOutTitle":null,"pinExecOutId":"515","outOrder":0}},"color":"FunctionBegin","rows":2,"colums":12,"isDeletable":false,"outputs":{"output0":{"outputTitle":"num","dataType":"Number","pinOutId":"516","outOrder":1}}}},{"position":{"x":752.6042405443744,"y":-146.98291402171543},"nodeDescription":{"nodeTitle":"If/Else","color":"Logic","rows":3,"colums":12,"execIn":true,"pinExecInId":"792","execOut":{"execOut0":{"execOutTitle":"True","pinExecOutId":"793","outOrder":0},"execOut1":{"execOutTitle":"False","pinExecOutId":"795","outOrder":1},"execOut2":{"execOutTitle":"Done","pinExecOutId":"797","outOrder":2}},"inputs":{"input0":{"inputTitle":"Bool","dataType":"Boolean","defValue":true,"pinInId":"799","isInputBoxRequired":true}}}},{"position":{"x":572.6646227778107,"y":15.169287303525039},"nodeDescription":{"nodeTitle":"Equals","color":"Logic","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Data","defValue":0,"pinInId":"817","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Data","defValue":"1","pinInId":"822","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Boolean","pinOutId":"827","outOrder":0}}}},{"position":{"x":1485.3160975104106,"y":460.73643125691024},"nodeDescription":{"nodeTitle":"Multiply","color":"Math","rows":2,"colums":10,"inputs":{"input0":{"inputTitle":"ValueA","dataType":"Number","defValue":0,"pinInId":"606","isInputBoxRequired":true},"input1":{"inputTitle":"ValueB","dataType":"Number","defValue":0,"pinInId":"611","isInputBoxRequired":true}},"outputs":{"output0":{"outputTitle":"Result","dataType":"Number","pinOutId":"616","outOrder":0}}}}],"wireData":[{"srcId":"1080","destId":"524"},{"srcId":"1053","destId":"523"},{"srcId":"905","destId":"1019"},{"srcId":"1020","destId":"523"},{"srcId":"712","destId":"906"},{"srcId":"516","destId":"702"},{"srcId":"515","destId":"792"},{"srcId":"793","destId":"1052"},{"srcId":"795","destId":"904"},{"srcId":"516","destId":"817"},{"srcId":"827","destId":"799"},{"srcId":"911","destId":"606"},{"srcId":"516","destId":"611"},{"srcId":"616","destId":"1021"}],"groupsData":[]}]} \ No newline at end of file diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..7e644f3 --- /dev/null +++ b/src/style.css @@ -0,0 +1,1068 @@ +*{ + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} +html{ + font-size: 10px; +} + + /* display: flex; */ + /* align-items: center; */ + +::-webkit-scrollbar{ + width: 7px; + /* border-radius: 5%; */ + +} +::-webkit-scrollbar-track{ + background-color:white; + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5); + /* border-radius: 50%; */ +} +::-webkit-scrollbar-thumb{ + background-color: #d65a31; + /* box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.25); */ + /* border-radius: 50%; */ +} + + +#container { + height: 100vh; + /* flex: 4; */ + top:0; + left: 0; + width: 100vw; + position: absolute; + overflow: hidden; + /* border: #f0f0f0 solid 7px; */ + /* border-left: #f0f0f0 solid 3px; */ + background-color: #26282b; + /* background-color: #b3b3b3; */ + /* background-image: url("images/sqq1.png"); */ + background-image: linear-gradient(rgb(92, 92, 92) .1rem, transparent .1rem), linear-gradient(90deg, rgb(92, 92, 92) .1rem, transparent .1rem); +background-size: 10rem 10rem; + background-position: 0px 0px; + /* background-size: 4%; */ + z-index: -2; + /* background-blend-mode:difference; */ + /* background-repeat:initial; */ + } + #left-panel{ + position: absolute; + top: 0; + left: 0; + /* flex: 1; */ + width: 20rem; + height: 100vh; + background-color: rgba(43, 46, 51, 0.4); + /* background-color: #2d2d38; */ + border-right: 1px solid #d65a31; + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); + z-index: 5; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items:center; + overflow-x: hidden; + overflow-y: auto; + transition: left 0.5s; + } + #create-variables, + .left-panel-section-inner{ + width: 85%; + margin: 0.4rem auto; + padding: 0.4rem 0; + box-sizing: border-box; + } + #input-params-section, + #output-params-section, + #doc-string-section, + #save-function-section{ + width: 100%; + } + #ctx-menu-container{ + position: absolute; + width: 210px; + height: 370px; + z-index: 20; + } + #ctx-search-bar{ + width: 100%; + outline: none; + transform-origin: top left; + font-size: 1rem; + border: 1px solid rgba(214, 90, 49, 0.6); + padding: 0.4rem 0.6rem; + background: rgba(18, 20, 26, 0.82); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + caret-color: #d65a31; + color: #f0f0f0; + border-radius: 8px 8px 0 0; + letter-spacing: 0.02em; + transition: border-color 0.2s; + } + #ctx-search-bar:focus{ + border-color: #d65a31; + } + #ctx-search-bar::placeholder{ + color: rgba(255, 255, 255, 0.4); + } + #context-menu{ + border: 1px solid rgba(214, 90, 49, 0.45); + border-top: none; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45); + width: 100%; + max-height: 330px; + min-height: 30px; + border-radius: 0 0 8px 8px; + overflow: auto; + font-size: 1rem; + font-family: Verdana, Geneva, Tahoma, sans-serif; + user-select: none; + color: white; + background: rgba(18, 20, 26, 0.82); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + } + #context-menu::-webkit-scrollbar{ + width: 5px; + } + #context-menu::-webkit-scrollbar-track{ + background: transparent; + } + #context-menu::-webkit-scrollbar-thumb{ + background: rgba(214, 90, 49, 0.35); + border-radius: 4px; + } + #context-menu::-webkit-scrollbar-thumb:hover{ + background: rgba(214, 90, 49, 0.55); + } + .ctx-menu-section{ + margin: 0; + } + .ctx-menu-section.hidden{ + display: none; + } + .ctx-menu-section + .ctx-menu-section{ + border-top: 1px solid rgba(255, 255, 255, 0.06); + } + .ctx-menu-section-header{ + padding: 5px 8px; + font-weight: 600; + font-size: 0.92em; + cursor: pointer; + border-left: 3px solid transparent; + display: flex; + align-items: center; + letter-spacing: 0.03em; + transition: background 0.15s; + } + .ctx-menu-section-header:hover{ + background: rgba(255, 255, 255, 0.06); + } + .ctx-menu-section-arrow{ + margin-right: 6px; + font-size: 0.7em; + opacity: 0.85; + transition: transform 0.15s; + } + .ctx-menu-section-body{ + padding: 2px 0; + } + .ctx-menu-section--collapsed .ctx-menu-section-body{ + display: none; + } + .context-menu-items{ + padding: 3px 8px 3px 16px; + border-left: 3px solid transparent; + font-size: 0.92em; + cursor: pointer; + transition: background 0.12s; + } + .context-menu-items.hidden{ + display: none; + } + .context-menu-items:hover{ + background: rgba(255, 255, 255, 0.10); + } + .ctx-focus{ + background-color: #2d2d38; + color: white; + } +.icon2{ + width: 60px; + padding: 30px; + height: 30px; + padding-top: 20px; + padding-bottom: 20px; + display: flex; + align-items:center; + justify-content: center; + font-size: 1.2rem; + font-family: Verdana, Geneva, Tahoma, sans-serif; + outline: none; + border: none; + background-color: #d65a31; + z-index: 10; + color: white; + user-select: none; +} +.icon4{ + width: 60px; + padding: 30px; + height: 30px; + padding-top: 20px; + padding-bottom: 20px; + display: flex; + align-items:center; + justify-content: center; + font-size: 1.2rem; + font-family: Verdana, Geneva, Tahoma, sans-serif; + outline: none; + border: none; + background-color: #ccff33; + z-index: 10; + color: rgb(20, 63, 20); + user-select: none; +} +.icon4:hover{ + color: rgb(20, 63, 20); + background-color: white; +} +.icon3{ + /* width: 60px; + padding: 30px; + height: 30px; + padding-top: 20px; + padding-bottom: 20px; */ + display: flex; + align-items:center; + justify-content: center; + font-size: 1.2rem; + font-family: Verdana, Geneva, Tahoma, sans-serif; + outline: none; + border: none; + background-color: transparent; + z-index: 10; + color: white; + user-select: none; +} +.ipn, .ips{ + display:none; + position: absolute; + left: 0; + top: 0; + width: 50px; + height: 14px; + transform-origin: top left; + opacity: 0.8; + outline: none; + font-size: 10px; + /* caret-color: red; */ + background-color:#19214e; + color: white; + border: none; + /* transform: scaleY(2); */ +} +.ipb{ + display:none; + position: absolute; + /* appearance: none; */ + padding: 0; + left: 0; + top: 0; + width: 50px; + height: 14px; + font-size:10px; + transform-origin: top left; + opacity: 0.8; + outline: none; + background-color:#19214e; + color: white; + border: none; + +} + +#group-name-ip{ + width: 120px; + height: 18px; + font-size: 13px; + background-color: rgba(30, 35, 50, 0.95); + z-index: 100; +} + +.rightbar{ + position: absolute; + right:0px; + margin: 0px; + top: 0px; + display: flex; + flex-direction: row-reverse; + align-items: center; + justify-content: center; + box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); + /* border: 1px solid white; */ +} +.icon{ + width: 60px; + padding: 30px; + height: 30px; + padding-top: 20px; + padding-bottom: 20px; + display: flex; + align-items:center; + justify-content: center; + font-size: 1.2rem; + font-family: Verdana, Geneva, Tahoma, sans-serif; + outline: none; + border: none; + background-color: #d65a31; + z-index: 10; + color: white; + user-select: none; +} +.icon:hover{ + color: #d65a31; + background-color: white; +} + + +#console-window{ + position: absolute; + width: 75%; + right: 2%; + height: 200px; + bottom: 0px; + /* border-radius: 3px; */ + overflow: hidden; + background-color: rgba(11, 11, 12, 0.4); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + z-index: 3; + /* background-color: transparent; */ + opacity: 0.9; + border: 1px solid #d65a31; + /* box-shadow: 15px 15px 15px rgba(0, 0, 0, 0.5); */ + color: white; +} + +.cross{ + position: absolute; + right: 0; + color:white; + /* font-size: 30px; */ + /* transform: translateY(-10px); */ + background-color: #d65a31; + border: none; + outline: none; + width: 25px; + height: 25px; +} +.cross div{ + transform: rotateZ(45deg); + font-size: 15px; +} +.cross:hover{ + color: #d65a31; + background-color: white; +} +iframe{ + position: relative; + top: 25px; + width: 100%; +} +.hidden{ + display: none !important; +} +.sidebox{ + position: absolute; + transform-origin: center center; + height: 150px; + width: 400px; + background-color: rgba(43, 46, 51, 0.4); + left: 500px; + top:20px; + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); + border: 1px solid #d65a31; + /* display:flex; + flex-direction: column; + justify-content: space-around; + align */ + /* transform:translateX(50%) translateY(50%); */ + animation: border-anim; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; +} +.sidebox-flex{ + width: 100%; + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; +} +@keyframes border-anim{ + 0% {border-color: #d65a31;} + 50% {border-color: transparent;} + 100% {border-color: #d65a31;} +} +/* ---------- Import modal ---------- */ +#import-overlay{ + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.55); + z-index: 500; + display: flex; + align-items: center; + justify-content: center; +} +#import-modal{ + width: 400px; + max-width: 92vw; + background: rgba(43, 46, 51, 0.96); + border: 1px solid #d65a31; + border-radius: 8px; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + display: flex; + flex-direction: column; + overflow: hidden; + color: white; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} +.import-modal-header{ + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.7rem 1rem; + border-bottom: 1px solid rgba(255,255,255,0.1); +} +.import-modal-title{ + font-size: 1.3rem; + font-weight: 600; +} +.import-modal-close{ + background: none; + border: none; + color: rgba(255,255,255,0.6); + font-size: 1.6rem; + cursor: pointer; + line-height: 1; + padding: 0 0.2rem; +} +.import-modal-close:hover{ + color: #d65a31; +} +#import-dropzone{ + margin: 1rem; + padding: 2rem 1rem; + border: 2px dashed rgba(255,255,255,0.25); + border-radius: 6px; + text-align: center; + cursor: pointer; + transition: border-color 0.2s, background 0.2s; +} +#import-dropzone:hover, +#import-dropzone.drag-over{ + border-color: #d65a31; + background: rgba(214, 90, 49, 0.08); +} +.import-dropzone-icon{ + font-size: 2.4rem; + color: rgba(255,255,255,0.4); + margin-bottom: 0.5rem; +} +.import-dropzone-text{ + font-size: 0.95rem; + color: rgba(255,255,255,0.65); + margin: 0; +} +#import-dropzone input[type="file"]{ + display: none; +} +#import-file-info{ + margin: 0 1rem 0.6rem; + padding: 0.5rem 0.7rem; + background: rgba(255,255,255,0.06); + border-radius: 4px; + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.95rem; + color: #ccc; +} +#import-filename{ + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +#import-file-clear{ + background: none; + border: none; + color: rgba(255,255,255,0.5); + font-size: 1.2rem; + cursor: pointer; + padding: 0 0.2rem; +} +#import-file-clear:hover{ + color: #f44; +} +.import-modal-warning{ + margin: 0 1rem 0.8rem; + font-size: 0.85rem; + color: #e8a040; +} +.import-modal-warning i{ + margin-right: 0.3rem; +} +.import-modal-footer{ + display: flex; + justify-content: flex-end; + gap: 0.6rem; + padding: 0.7rem 1rem; + border-top: 1px solid rgba(255,255,255,0.1); +} +.import-btn-primary, +.import-btn-secondary{ + padding: 0.4rem 1.2rem; + font-size: 1rem; + font-family: inherit; + border: none; + border-radius: 4px; + cursor: pointer; +} +.import-btn-primary{ + background: #d65a31; + color: white; +} +.import-btn-primary:hover{ + background: #e86b40; +} +.import-btn-secondary{ + background: rgba(255,255,255,0.12); + color: white; +} +.import-btn-secondary:hover{ + background: rgba(255,255,255,0.22); +} +#delete-ctx-container{ + position: absolute; + z-index: 200; + display: flex; + justify-content: center; + align-items:center; + flex-direction: column; + overflow: auto; + font-size: 1rem; + font-family: Verdana, Geneva, Tahoma, sans-serif; + user-select: none; + color: white; + border: solid 1px #d65a31; + background-color: rgba(43, 46, 51, 0.85); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + border-radius: 6px; + width: 100px !important; +} +#delete-ctx-container:hover{ + background-color: rgba(214, 90, 49, 0.85); + color: white; +} +#get-set-ctx-menu-container{ + position: absolute; + border: #d65a31 solid 1px; + box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); + width: 80px !important; + overflow: auto; + font-size: 1rem; + font-family: Verdana, Geneva, Tahoma, sans-serif; + user-select: none; + color: white; + background-color: rgba(43, 46, 51, 0.85); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + border-radius: 6px; + z-index: 200; +} +.left-panel-variable{ + width: 85%; + font-size: 1rem; + margin: 0.2rem auto; + padding: 0.25rem 0.5rem; + cursor: grab; + user-select: none; + color: white !important; + overflow: hidden; + display: flex !important; + flex-direction: row !important; + flex-wrap: nowrap; + align-items: center; + border-radius: 5px; + justify-content: space-between; +} +.left-panel-variable .var-actions{ + display: flex; + gap: 0.4rem; + margin-left: auto; + flex-shrink: 0; + opacity: 0; + transition: opacity 0.15s; +} +.left-panel-variable:hover .var-actions{ + opacity: 1; +} +.left-panel-variable .var-actions button{ + background: none; + border: none; + color: rgba(255,255,255,0.5); + cursor: pointer; + font-size: 1rem; + padding: 0.1rem 0.25rem; + transition: color 0.15s; +} +.left-panel-variable .var-actions button:hover{ + color: #fff; +} +.left-panel-variable .var-actions .var-delete-btn:hover{ + color: #f44; +} +.save-function-btn{ + width: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; + padding: 0.5rem 0.75rem; + font-size: 0.9rem; + white-space: nowrap; + border: 1px solid #00bfa5; + border-radius: 6px; + background: #00897b; + color: #fff; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; +} +.save-function-btn:hover{ + background: #00a090; + border-color: #00e5cc; + color: #fff; +} + +.call-node-doc-tooltip{ + position: fixed; + z-index: 10000; + max-width: 260px; + padding: 8px 10px; + font-size: 12px; + font-family: Verdana, sans-serif; + line-height: 1.35; + color: #eee; + background: rgba(30, 30, 35, 0.97); + border: 1px solid #00bfa5; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.4); + pointer-events: none; + display: none; + word-wrap: break-word; +} + +.function-list-doc-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-left: 6px; + font-size: 11px; + font-weight: bold; + font-family: Verdana, sans-serif; + color: #00bfa5; + cursor: help; + flex-shrink: 0; +} + +.function-list-doc-icon:hover { + color: #00e5cc; +} + +.variableList{ + display: flex; + width: 100%; + /* align-items: flex-start; */ + flex-direction: column-reverse; + justify-content: flex-end; +} +.slider-icon{ + display: flex; + justify-content:center; + align-items:center; + position: absolute; + height: 2.5rem; + width: 2.5rem; + background-color:transparent; + color: white; + right: 0; + top:0; + font-size: 2rem; + user-select: none; + /* transition: transform 0.5s; */ + /* border: 1px solid #d65a31; */ + z-index: 10 ; +} +.slider-icon:hover{ + /* background-color:transparent; */ + color: #d65a31; + /* transform: scale(1.1); */ +} +.closed-left-panel{ + left: -17rem !important; +} +.slider-icon div{ + transition: transform 0.5s; +} +.slider-icon-closed{ + transform: rotateZ(180deg) !important; +} +.left-panel-content{ + width: 100%; + display: flex; + align-items:flex-start; + justify-content: flex-start; + flex-direction: column; + transform: translateY(3rem); +} +.left-panel-tab{ + width: 100%; + background-color: rgba(255,255,255,0.03); + color:white; + height: 2rem; + font-size: 1.2rem; + border: solid 1px white; + border-width: 0 0 1px 0; + border-left: 3px solid #d65a31; + border-radius: 0 6px 6px 0; + padding: 0.8rem; + padding-left: 1rem; + margin-top: 0.6rem; + display: flex; + justify-content: flex-start; + align-items: center; + user-select: none; + overflow:visible; +} +.left-panel-tab:hover{ + background-color:#d65a31; + color:white; +} +.left-panel-tab-content{ + margin-top: 0.5rem; + width: 100%; + box-sizing: border-box; +} +.left-panel-tab-arrow-up{ + transform: rotateZ(90deg) !important; + /* content:; */ +} +.live-code-container{ + right: 0rem; + top:7rem; + /* position: absolute; */ + width: 55.2rem; + height: 57rem; + display: flex; + justify-content: flex-start; + align-items: flex-start; + flex-direction: column; + background-color: rgba(43, 46, 51, 0.4); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + border: #d65a31 solid 1px; + position: fixed; + transition: right 0.5s; + overflow: hidden; + box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); +} +.icons-live-code{ + display: flex; + justify-content: flex-start; + align-items: center; + color: white; + height: 5rem; +} +.icons-live-code div{ + display: flex; + justify-content:center; + align-items:center; + /* position: absolute; */ + height: 2.5rem; + padding: 0rem 2rem; + width: 2.5rem; + background-color:transparent; + color: white; + font-size: 2rem; + transition: 0.5s transform; +} +.icons-live-code div:hover{ + /* background-color:transparent; */ + color: #d65a31; + /* transform: scale(1.1); */ +} +.live-code-closed{ + right: -52rem !important; +} +.live-code-arrow-clicked{ + transform: rotateZ(180deg) !important; +} +#live-code{ + align-self: flex-end; + width: 55rem; + height: 55rem; + font-size:1.5rem; + overflow: auto; +} +/* #Github a{ + display: inline-block; + color: white; + width: 60px; + height:; +} +#Github a:hover{ + color: #d65a31; +} */ +.var-inline-edit{ + width: 100%; + display: flex; + flex-direction: column; + gap: 0.2rem; + padding: 0.2rem; +} +.var-inline-edit input, +.var-inline-edit select{ + width: 100%; + box-sizing: border-box; + font-size: 0.9rem; + padding: 0.2rem 0.4rem; + background: rgba(255,255,255,0.1); + border: 1px solid rgba(255,255,255,0.3); + color: white; + outline: none; + border-radius: 2px; +} +.var-inline-edit select option{ + background: #2b2e33; + color: white; +} +.var-inline-edit input:focus, +.var-inline-edit select:focus{ + border-color: #d65a31; +} +.var-inline-edit .var-edit-actions{ + display: flex; + gap: 0.3rem; + justify-content: flex-end; +} +.var-inline-edit .var-edit-actions button{ + font-size: 0.85rem; + padding: 0.15rem 0.5rem; + border: none; + cursor: pointer; + border-radius: 2px; +} +.var-edit-save{ + background: #d65a31; + color: white; +} +.var-edit-save:hover{ + background: #e86b40; +} +.var-edit-cancel{ + background: rgba(255,255,255,0.15); + color: white; +} +.var-edit-cancel:hover{ + background: rgba(255,255,255,0.25); +} +#create-variables .var-edit-actions{ + justify-content: stretch; +} +#create-variables .var-edit-save{ + width: 100%; + text-align: center; +} +#confirm-dialog{ + z-index: 300; +} +#confirm-dialog .sidebox-flex{ + flex-direction: column; + gap: 0.5rem; + padding: 1rem; +} +#confirm-dialog .confirm-actions{ + display: flex; + gap: 0.5rem; + justify-content: center; +} +/* ── Tab bar ── */ +#tab-bar{ + position: absolute; + top: 0; + left: 20rem; + height: 3rem; + display: flex; + align-items: center; + z-index: 4; + background: rgba(30, 32, 36, 0.88); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + border-bottom: 1px solid rgba(214, 90, 49, 0.3); + user-select: none; + transition: left 0.5s; +} +#left-panel.closed-left-panel ~ #tab-bar{ + left: 3rem; +} +#tab-list{ + display: flex; + align-items: center; + height: 100%; +} +.tab-item{ + display: flex; + align-items: center; + padding: 0 1.2rem; + height: 100%; + color: rgba(255,255,255,0.55); + font-size: 1.1rem; + cursor: pointer; + white-space: nowrap; + border-right: 1px solid rgba(255,255,255,0.08); + transition: background 0.15s, color 0.15s; + position: relative; +} +.tab-item:hover{ + background: rgba(255,255,255,0.07); + color: #fff; +} +.tab-item.tab-active{ + background: rgba(214, 90, 49, 0.18); + color: #fff; + box-shadow: inset 0 -2px 0 #d65a31; +} +.tab-name{ + pointer-events: auto; +} +.tab-close{ + margin-left: 0.6rem; + font-size: 1.3rem; + line-height: 1; + opacity: 0; + transition: opacity 0.15s, color 0.15s; +} +.tab-item:hover .tab-close{ + opacity: 0.5; +} +.tab-close:hover{ + opacity: 1 !important; + color: #f44; +} +#add-tab-btn{ + display: flex; + align-items: center; + justify-content: center; + height: 2.2rem; + width: 2.2rem; + margin-left: 0.4rem; + background: none; + border: 1px solid rgba(255,255,255,0.15); + border-radius: 4px; + color: rgba(255,255,255,0.45); + font-size: 1.5rem; + cursor: pointer; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} +#add-tab-btn:hover{ + background: rgba(214, 90, 49, 0.25); + color: #fff; + border-color: #d65a31; +} +.tab-rename-input{ + background: rgba(255,255,255,0.1); + border: 1px solid #d65a31; + color: #fff; + font-size: 1.1rem; + font-family: inherit; + padding: 0.1rem 0.4rem; + outline: none; + width: 10rem; + border-radius: 2px; +} +/* ── Input/Output Parameters: same alignment as Add Variable, Variable List, Functions ── */ +.left-panel-section-inner .var-inline-edit{ + width: 100%; +} +.param-def-value-container{ + width: 100%; +} +.param-def-value-container .param-def-value{ + width: 100%; + box-sizing: border-box; +} +.left-panel-section-inner .var-edit-actions{ + justify-content: stretch; +} +.left-panel-section-inner .var-edit-save{ + width: 100%; + text-align: center; +} +.left-panel-section-inner .param-list{ + list-style: none; + padding: 0; + margin: 0.5rem 0 0; + display: flex; + flex-direction: column; + width: 100%; +} +.left-panel-section-inner .param-list .left-panel-variable{ + width: 100%; + margin: 0.2rem 0; + box-sizing: border-box; +} + +.doc-string-textarea{ + width: 100%; + box-sizing: border-box; + font-size: 0.9rem; + padding: 0.3rem 0.4rem; + background: rgba(255,255,255,0.1); + border: 1px solid rgba(255,255,255,0.3); + color: white; + outline: none; + border-radius: 2px; + resize: vertical; + min-height: 4rem; + font-family: inherit; +} +.doc-string-textarea:focus{ + border-color: #d65a31; +} +.doc-string-textarea::placeholder{ + color: rgba(255,255,255,0.4); +} + +.param-list{ + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + width: 100%; +} \ No newline at end of file diff --git a/javascript/Dependencies/CodeMirror/ayu-dark.css b/src/vendor/codemirror/ayu-dark.css similarity index 100% rename from javascript/Dependencies/CodeMirror/ayu-dark.css rename to src/vendor/codemirror/ayu-dark.css diff --git a/javascript/Dependencies/CodeMirror/ayu-mirage.css b/src/vendor/codemirror/ayu-mirage.css similarity index 100% rename from javascript/Dependencies/CodeMirror/ayu-mirage.css rename to src/vendor/codemirror/ayu-mirage.css diff --git a/javascript/Dependencies/CodeMirror/codemirror.css b/src/vendor/codemirror/codemirror.css similarity index 100% rename from javascript/Dependencies/CodeMirror/codemirror.css rename to src/vendor/codemirror/codemirror.css diff --git a/javascript/Dependencies/CodeMirror/codemirror.js b/src/vendor/codemirror/codemirror.js similarity index 100% rename from javascript/Dependencies/CodeMirror/codemirror.js rename to src/vendor/codemirror/codemirror.js diff --git a/javascript/Dependencies/CodeMirror/javascript/index.html b/src/vendor/codemirror/javascript/index.html similarity index 100% rename from javascript/Dependencies/CodeMirror/javascript/index.html rename to src/vendor/codemirror/javascript/index.html diff --git a/javascript/Dependencies/CodeMirror/javascript/javascript.js b/src/vendor/codemirror/javascript/javascript.js similarity index 100% rename from javascript/Dependencies/CodeMirror/javascript/javascript.js rename to src/vendor/codemirror/javascript/javascript.js diff --git a/javascript/Dependencies/CodeMirror/javascript/json-ld.html b/src/vendor/codemirror/javascript/json-ld.html similarity index 100% rename from javascript/Dependencies/CodeMirror/javascript/json-ld.html rename to src/vendor/codemirror/javascript/json-ld.html diff --git a/javascript/Dependencies/CodeMirror/javascript/test.js b/src/vendor/codemirror/javascript/test.js similarity index 100% rename from javascript/Dependencies/CodeMirror/javascript/test.js rename to src/vendor/codemirror/javascript/test.js diff --git a/javascript/Dependencies/CodeMirror/javascript/typescript.html b/src/vendor/codemirror/javascript/typescript.html similarity index 100% rename from javascript/Dependencies/CodeMirror/javascript/typescript.html rename to src/vendor/codemirror/javascript/typescript.html diff --git a/javascript/Dependencies/CodeMirror/material.css b/src/vendor/codemirror/material.css similarity index 100% rename from javascript/Dependencies/CodeMirror/material.css rename to src/vendor/codemirror/material.css diff --git a/javascript/Dependencies/jquery/jquery.js b/src/vendor/jquery/jquery.js similarity index 100% rename from javascript/Dependencies/jquery/jquery.js rename to src/vendor/jquery/jquery.js diff --git a/javascript/Dependencies/Konva/konva.min.js b/src/vendor/konva/konva.min.js similarity index 100% rename from javascript/Dependencies/Konva/konva.min.js rename to src/vendor/konva/konva.min.js diff --git a/style.css b/style.css deleted file mode 100644 index 677691a..0000000 --- a/style.css +++ /dev/null @@ -1,550 +0,0 @@ -*{ - margin: 0; - padding: 0; - box-sizing: border-box; - font-family: Verdana, Geneva, Tahoma, sans-serif; -} -html{ - font-size: 10px; -} - - /* display: flex; */ - /* align-items: center; */ - -::-webkit-scrollbar{ - width: 7px; - /* border-radius: 5%; */ - -} -::-webkit-scrollbar-track{ - background-color:white; - box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5); - /* border-radius: 50%; */ -} -::-webkit-scrollbar-thumb{ - background-color: #d65a31; - /* box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.25); */ - /* border-radius: 50%; */ -} - - -#container { - height: 100vh; - /* flex: 4; */ - top:0; - left: 0; - width: 100vw; - position: absolute; - overflow: hidden; - /* border: #f0f0f0 solid 7px; */ - /* border-left: #f0f0f0 solid 3px; */ - background-color: #26282b; - /* background-color: #b3b3b3; */ - /* background-image: url("images/sqq1.png"); */ - background-image: linear-gradient(rgb(92, 92, 92) .1rem, transparent .1rem), linear-gradient(90deg, rgb(92, 92, 92) .1rem, transparent .1rem); -background-size: 10rem 10rem; - background-position: 0px 0px; - /* background-size: 4%; */ - z-index: -2; - /* background-blend-mode:difference; */ - /* background-repeat:initial; */ - } - #left-panel{ - position: absolute; - top: 0; - left: 0; - /* flex: 1; */ - width: 25rem; - height: 100vh; - background-color: rgba(43, 46, 51, 0.4); - /* background-color: #2d2d38; */ - border-right: 1px solid #d65a31; - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); - z-index: 5; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items:center; - overflow: auto; - transition: left 0.5s; - } - .slider - #create-variables{ - /* height: ; */ - width: 100%; - } - #ctx-menu-container{ - position: absolute; - width: 200px; - height: 250px; - /* overflow: hidden; */ - z-index: 20; - } - #ctx-search-bar{ - width: 100%; - outline:none; - transform-origin: top left; - font-size: 1.5rem; - /* height: 2.5rem; */ - border: #d65a31 solid 1px; - padding: 0.5rem; - background-color: rgb(230, 221, 221, 0.3); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - caret-color: white; - color: white; - border-radius:0; - } - #ctx-search-bar::placeholder{ - color: rgb(230, 221, 221); - } - #context-menu{ - /* display: none; */ - /* position: absolute; */ - border: #d65a31 solid 1px; - box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); - margin-top: 1px; - border-top: none; - /* border-radius: 2px; */ - /* cursor:pointer; */ - /* padding: 10px 0px; */ - width: 100%; - max-height: 225px; - min-height: 35px; - /* border-radius: 2px; */ - overflow: auto; - font-size: 1.5rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; - /* padding: 0.5rem; */ - user-select: none; - color: black; - /* background-color: white; */ - background-color: rgba(255, 255, 255, 0.7); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - } - .context-menu-items{ - padding: 5px; - - } - - .context-menu-items:hover{ - background-color: #d65a31; - color: white; - /* z-index: 20; */ - /* cursor:pointer; */ - - } - .ctx-focus{ - background-color: #2d2d38; - color: white; - } -.icon2{ - width: 60px; - padding: 30px; - height: 30px; - padding-top: 20px; - padding-bottom: 20px; - display: flex; - align-items:center; - justify-content: center; - font-size: 1.2rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; - outline: none; - border: none; - background-color: #d65a31; - z-index: 10; - color: white; - user-select: none; -} -.icon4{ - width: 60px; - padding: 30px; - height: 30px; - padding-top: 20px; - padding-bottom: 20px; - display: flex; - align-items:center; - justify-content: center; - font-size: 1.2rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; - outline: none; - border: none; - background-color: #ccff33; - z-index: 10; - color: rgb(20, 63, 20); - user-select: none; -} -.icon4:hover{ - color: rgb(20, 63, 20); - background-color: white; -} -.icon3{ - /* width: 60px; - padding: 30px; - height: 30px; - padding-top: 20px; - padding-bottom: 20px; */ - display: flex; - align-items:center; - justify-content: center; - font-size: 1.2rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; - outline: none; - border: none; - background-color: transparent; - z-index: 10; - color: white; - user-select: none; -} -.ipn, .ips{ - display:none; - position: absolute; - left: 0; - top: 0; - width: 50px; - height: 14px; - transform-origin: top left; - opacity: 0.8; - outline: none; - font-size: 10px; - /* caret-color: red; */ - background-color:#19214e; - color: white; - border: none; - /* transform: scaleY(2); */ -} -.ipb{ - display:none; - position: absolute; - /* appearance: none; */ - padding: 0; - left: 0; - top: 0; - width: 50px; - height: 14px; - font-size:10px; - transform-origin: top left; - opacity: 0.8; - outline: none; - background-color:#19214e; - color: white; - border: none; - -} - - -.rightbar{ - position: absolute; - right:0px; - margin: 0px; - top: 0px; - display: flex; - flex-direction: row-reverse; - align-items: center; - justify-content: center; - box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); - /* border: 1px solid white; */ -} -.icon{ - width: 60px; - padding: 30px; - height: 30px; - padding-top: 20px; - padding-bottom: 20px; - display: flex; - align-items:center; - justify-content: center; - font-size: 1.2rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; - outline: none; - border: none; - background-color: #d65a31; - z-index: 10; - color: white; - user-select: none; -} -.icon:hover{ - color: #d65a31; - background-color: white; -} - - -#console-window{ - position: absolute; - width: 75%; - right: 2%; - height: 200px; - bottom: 0px; - /* border-radius: 3px; */ - overflow: hidden; - background-color: rgba(11, 11, 12, 0.4); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - z-index: 3; - /* background-color: transparent; */ - opacity: 0.9; - border: 1px solid #d65a31; - /* box-shadow: 15px 15px 15px rgba(0, 0, 0, 0.5); */ - color: white; -} - -.cross{ - position: absolute; - right: 0; - color:white; - /* font-size: 30px; */ - /* transform: translateY(-10px); */ - background-color: #d65a31; - border: none; - outline: none; - width: 25px; - height: 25px; -} -.cross div{ - transform: rotateZ(45deg); - font-size: 15px; -} -.cross:hover{ - color: #d65a31; - background-color: white; -} -iframe{ - position: relative; - top: 25px; - width: 100%; -} -.hidden{ - display: none !important; -} -.sidebox{ - position: absolute; - transform-origin: center center; - height: 150px; - width: 400px; - background-color: rgba(43, 46, 51, 0.4); - left: 500px; - top:20px; - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); - border: 1px solid #d65a31; - /* display:flex; - flex-direction: column; - justify-content: space-around; - align */ - /* transform:translateX(50%) translateY(50%); */ - animation: border-anim; - animation-duration: 2s; - animation-iteration-count: infinite; - animation-timing-function: ease-in-out; -} -.sidebox-flex{ - width: 100%; - height: 100%; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-around; -} -@keyframes border-anim{ - 0% {border-color: #d65a31;} - 50% {border-color: transparent;} - 100% {border-color: #d65a31;} -} -input[type="file"] { - position: absolute; - display: none; - z-index: -100; - top: 15px; - left: 20px; - font-size: 17px; - color: #b8b8b8; -} -#delete-ctx-container{ - /* width: 150px !important; */ - position: absolute; - z-index: 200; - display: flex; - justify-content: center; - align-items:center; - flex-direction: column; - overflow: auto; - font-size: 1.5rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; - /* padding: 0.5rem; */ - user-select: none; - color: black; - border: solid 1px #d65a31; - /* background-color: white; */ - background-color: rgba(255, 255, 255, 0.7); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - width: 125px !important; -} -#delete-ctx-container:hover{ - background-color: #d65a31; - color: white; -} -#get-set-ctx-menu-container{ - position: absolute; - border: #d65a31 solid 1px; - box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); - width: 100px !important; - overflow: auto; - font-size: 1.5rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; - user-select: none; - color: black; - background-color: rgba(255, 255, 255, 0.7); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - z-index: 200; -} -.left-panel-variable{ - width: 100%; - font-size: 1.6rem; - margin:2rem 1rem 0 1rem; - cursor: grab; - user-select: none; - color: white !important; - overflow: hidden; -} -.variableList{ - display: flex; - width: 100%; - /* align-items: flex-start; */ - flex-direction: column-reverse; - justify-content: flex-end; -} -.slider-icon{ - display: flex; - justify-content:center; - align-items:center; - position: absolute; - height: 2.5rem; - width: 2.5rem; - background-color:transparent; - color: white; - right: 0; - top:0; - font-size: 2rem; - user-select: none; - /* transition: transform 0.5s; */ - /* border: 1px solid #d65a31; */ - z-index: 10 ; -} -.slider-icon:hover{ - /* background-color:transparent; */ - color: #d65a31; - /* transform: scale(1.1); */ -} -.closed-left-panel{ - left: -22rem !important; -} -.slider-icon div{ - transition: transform 0.5s; -} -.slider-icon-closed{ - transform: rotateZ(180deg) !important; -} -.left-panel-content{ - width: 100%; - display: flex; - align-items:flex-start; - justify-content: flex-start; - flex-direction: column; - transform: translateY(3rem); -} -.left-panel-tab{ - width: 100%; - background-color:transparent; - color:white; - height: 2rem; - font-size: 1.5rem; - border: solid 1px white; - border-width: 0 0 1px 0; - padding: 1.5rem; - margin-top: 0.5rem; - display: flex; - justify-content: flex-start; - align-items: center; - user-select: none; - overflow:visible; -} -.left-panel-tab:hover{ - background-color:#d65a31; - color:white; -} -.left-panel-tab-arrow-up{ - transform: rotateZ(90deg) !important; - /* content:; */ -} -.live-code-container{ - right: 0rem; - top:7rem; - /* position: absolute; */ - width: 55.2rem; - height: 57rem; - display: flex; - justify-content: flex-start; - align-items: flex-start; - flex-direction: column; - background-color: rgba(43, 46, 51, 0.4); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); - border: #d65a31 solid 1px; - position: fixed; - transition: right 0.5s; - overflow: hidden; - box-shadow: 15px 15px 25px rgba(0, 0, 0, 0.5); -} -.icons-live-code{ - display: flex; - justify-content: flex-start; - align-items: center; - color: white; - height: 5rem; -} -.icons-live-code div{ - display: flex; - justify-content:center; - align-items:center; - /* position: absolute; */ - height: 2.5rem; - padding: 0rem 2rem; - width: 2.5rem; - background-color:transparent; - color: white; - font-size: 2rem; - transition: 0.5s transform; -} -.icons-live-code div:hover{ - /* background-color:transparent; */ - color: #d65a31; - /* transform: scale(1.1); */ -} -.live-code-closed{ - right: -52rem !important; -} -.live-code-arrow-clicked{ - transform: rotateZ(180deg) !important; -} -#live-code{ - align-self: flex-end; - width: 55rem; - height: 55rem; - font-size:1.5rem; - overflow: auto; -} -/* #Github a{ - display: inline-block; - color: white; - width: 60px; - height:; -} -#Github a:hover{ - color: #d65a31; -} */ \ No newline at end of file diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..c4ece78 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import { cpSync } from 'node:fs'; + +export default defineConfig({ + root: 'src', + base: './', + build: { + outDir: '../dist', + emptyOutDir: true, + }, + plugins: [{ + name: 'copy-vendor', + closeBundle() { + cpSync('src/vendor', 'dist/vendor', { recursive: true }); + }, + }], +});