replace monaco with codemirror

This commit is contained in:
2026-03-06 23:22:48 +01:00
parent 49a6e5b4a3
commit 5feb2ea228
4 changed files with 341 additions and 243 deletions

319
package-lock.json generated
View File

@@ -8,19 +8,20 @@
"name": "reactflow-shadcn-app",
"version": "0.1.0",
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-yaml": "^6.0.2",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@uiw/react-codemirror": "^4.25.7",
"@xyflow/react": "^12.10.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"js-yaml": "^4.1.0",
"lucide-react": "^0.577.0",
"monaco-editor": "^0.53.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"reactflow": "^11.0.0",
@@ -492,6 +493,15 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -540,6 +550,129 @@
"node": ">=6.9.0"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
"integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz",
"integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/lang-javascript": {
"version": "6.2.5",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz",
"integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0",
"@lezer/javascript": "^1.0.0"
}
},
"node_modules/@codemirror/lang-yaml": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.2.0",
"@lezer/lr": "^1.0.0",
"@lezer/yaml": "^1.0.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz",
"integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.9.5",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz",
"integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz",
"integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.37.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz",
"integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/theme-one-dark": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/highlight": "^1.0.0"
}
},
"node_modules/@codemirror/view": {
"version": "6.39.16",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.16.tgz",
"integrity": "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@dotenvx/dotenvx": {
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.53.0.tgz",
@@ -1322,6 +1455,58 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lezer/common": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz",
"integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==",
"license": "MIT"
},
"node_modules/@lezer/highlight": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.3.0"
}
},
"node_modules/@lezer/javascript": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz",
"integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/yaml": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz",
"integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.4.0"
}
},
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.27.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
@@ -1363,29 +1548,6 @@
}
}
},
"node_modules/@monaco-editor/loader": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz",
"integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==",
"license": "MIT",
"dependencies": {
"state-local": "^1.0.6"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
"license": "MIT",
"dependencies": {
"@monaco-editor/loader": "^1.5.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mswjs/interceptors": {
"version": "0.41.3",
"resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz",
@@ -3158,12 +3320,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/trusted-types": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz",
"integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==",
"license": "MIT"
},
"node_modules/@types/validate-npm-package-name": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz",
@@ -3171,6 +3327,59 @@
"dev": true,
"license": "MIT"
},
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.25.7",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.7.tgz",
"integrity": "sha512-tPV/AGjF4yM22D5mnyH7EuYBkWO05wF5Y4x3lmQJo6LuHmhjh0RQsVDjqeIgNOkXT3UO9OdkL4dzxw465/JZVg==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@codemirror/autocomplete": ">=6.0.0",
"@codemirror/commands": ">=6.0.0",
"@codemirror/language": ">=6.0.0",
"@codemirror/lint": ">=6.0.0",
"@codemirror/search": ">=6.0.0",
"@codemirror/state": ">=6.0.0",
"@codemirror/view": ">=6.0.0"
}
},
"node_modules/@uiw/react-codemirror": {
"version": "4.25.7",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.7.tgz",
"integrity": "sha512-s/EbEe0dFANWEgfLbfdIrrOGv0R7M1XhkKG3ShroBeH6uP9pVNQy81YHOLRCSVcytTp9zAWRNfXR/+XxZTvV7w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0",
"@uiw/codemirror-extensions-basic-setup": "4.25.7",
"codemirror": "^6.0.0"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.11.0",
"@codemirror/state": ">=6.0.0",
"@codemirror/theme-one-dark": ">=6.0.0",
"@codemirror/view": ">=6.0.0",
"codemirror": ">=6.0.0",
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
}
},
"node_modules/@vitejs/plugin-react": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
@@ -3831,6 +4040,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/codemirror": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3957,6 +4181,12 @@
}
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -5705,15 +5935,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/monaco-editor": {
"version": "0.53.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.53.0.tgz",
"integrity": "sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==",
"license": "MIT",
"dependencies": {
"@types/trusted-types": "^1.0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -7114,12 +7335,6 @@
"node": ">=0.10.0"
}
},
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
"license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -7225,6 +7440,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/style-mod": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
"license": "MIT"
},
"node_modules/sucrase": {
"version": "3.35.1",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
@@ -7788,6 +8009,12 @@
}
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

View File

@@ -8,7 +8,9 @@
"preview": "vite preview"
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-yaml": "^6.0.2",
"@uiw/react-codemirror": "^4.25.7",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-select": "^2.2.6",
@@ -20,7 +22,6 @@
"clsx": "^2.1.1",
"js-yaml": "^4.1.0",
"lucide-react": "^0.577.0",
"monaco-editor": "^0.53.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"reactflow": "^11.0.0",

View File

@@ -1,5 +1,6 @@
import React, { memo, useCallback, useContext, useEffect, useRef, useState } from 'react'
import Editor, { loader } from '@monaco-editor/react'
import React, { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import CodeMirror from '@uiw/react-codemirror'
import { yaml } from '@codemirror/lang-yaml'
import FlowContext from '../../lib/flowContext'
import {
BaseNode,
@@ -13,9 +14,7 @@ import {
Menubar,
MenubarContent,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarSeparator,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
@@ -23,9 +22,6 @@ import {
} from '../ui/menubar'
import { InputHandle, OutputHandle } from './NodeHandles'
// Ensure Monaco can load its web workers under Vite by pointing to a CDN
loader.config({ paths: { vs: 'https://unpkg.com/monaco-editor@latest/min/vs' } })
type Props = {
id: string
data: any
@@ -38,8 +34,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data }: Props) {
const setNodes = ctx?.setNodes
const storedYaml = ctx?.nodes?.find((n: any) => n.id === id)?.data?.yaml ?? ''
const editorRef = useRef<any>(null)
const monacoRef = useRef<any>(null)
const editorRef = useRef<unknown>(null)
const incomingEdges = ctx?.edges?.filter((e: any) => e.target === id) ?? []
const incomingIds = incomingEdges.map((e: any) => e.source).sort()
@@ -56,166 +51,69 @@ export const ConfigNode = memo(function ConfigNode({ id, data }: Props) {
}, [])
const onChange = useCallback(
(val?: string) => {
const v = val ?? ''
setValue(v)
// eslint-disable-next-line no-console
console.debug('ConfigNode:onChange', id, v.substring(0, 60))
(val: string) => {
setValue(val)
if (setNodes) {
setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, yaml: v } } : n)))
setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, yaml: val } } : n)))
}
},
[id, setNodes]
)
const handleEditorMount = useCallback((editor: any, monaco: any) => {
editorRef.current = editor
monacoRef.current = monaco
// Prevent React Flow and other global handlers from interfering with typing
try {
editor.onKeyDown((e: any) => {
if (e?.browserEvent) {
e.browserEvent.stopPropagation()
const insertAt = useCallback(
(insertText: string, mode: 'prepend' | 'append' | 'cursor') => {
const ref = editorRef.current as { view: { state: { doc: { length: number; toString(): string }; selection: { main: { from: number } } }; dispatch: (arg: { changes: { from: number; to: number; insert: string } }) => void } } | null
if (ref?.view) {
const view = ref.view
const doc = view.state.doc
const len = doc.length
let from: number
if (mode === 'prepend') {
from = 0
} else if (mode === 'append') {
from = len
} else {
const main = view.state.selection.main
from = main.from
}
})
editor.onMouseDown((e: any) => {
if (e?.event) {
e.event.stopPropagation()
view.dispatch({ changes: { from, to: from, insert: insertText } })
const newVal = view.state.doc.toString()
onChange(newVal)
return
}
})
} catch {
// ignore if Monaco internals change
if (mode === 'prepend') {
onChange(insertText + value)
} else {
onChange(value + insertText)
}
}, [])
},
[onChange, value]
)
const insertIncludeFromNode = useCallback(
(sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
const ref = `${sourceNode.id}.yaml`
const includeText = `!include ${ref}\n`
const editor = editorRef.current
const monaco = monacoRef.current
try {
if (editor && monaco) {
const model = editor.getModel()
const lineCount = model.getLineCount()
const selection = editor.getSelection()
let range
if (mode === 'prepend') {
range = new monaco.Range(1, 1, 1, 1)
} else if (mode === 'append') {
range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
} else if (selection) {
range = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.startLineNumber, selection.startColumn)
} else {
range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
}
editor.executeEdits('insert-include', [{ range, text: includeText, forceMoveMarkers: true }])
const newVal = editor.getModel().getValue()
onChange(newVal)
return
}
if (mode === 'prepend') {
onChange(includeText + value)
} else {
onChange(value + '\n' + includeText)
}
} catch (err) {
// eslint-disable-next-line no-console
console.error('Insert include failed', err)
}
insertAt(`!include ${ref}\n`, mode)
},
[onChange, value]
[insertAt]
)
const insertVariableReference = useCallback(
(sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
const insertText = `\${${sourceNode.id}}`
const editor = editorRef.current
const monaco = monacoRef.current
try {
if (editor && monaco) {
const model = editor.getModel()
const lineCount = model.getLineCount()
const selection = editor.getSelection()
let range
if (mode === 'prepend') {
range = new monaco.Range(1, 1, 1, 1)
} else if (mode === 'append') {
range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
} else if (selection) {
range = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.startLineNumber, selection.startColumn)
} else {
range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
}
editor.executeEdits('insert-var', [{ range, text: insertText, forceMoveMarkers: true }])
const newVal = editor.getModel().getValue()
onChange(newVal)
return
}
if (mode === 'prepend') {
onChange(insertText + value)
} else {
onChange(value + insertText)
}
} catch (err) {
// eslint-disable-next-line no-console
console.error('Insert variable reference failed', err)
}
insertAt(`\${${sourceNode.id}}`, mode)
},
[onChange, value]
[insertAt]
)
const insertFunctionCall = useCallback(
(sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
const insertText = `\${${sourceNode.id}()}` // user can add variable ids inside: ${funcId(var1, var2)}
const editor = editorRef.current
const monaco = monacoRef.current
try {
if (editor && monaco) {
const model = editor.getModel()
const lineCount = model.getLineCount()
const selection = editor.getSelection()
let range
if (mode === 'prepend') {
range = new monaco.Range(1, 1, 1, 1)
} else if (mode === 'append') {
range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
} else if (selection) {
range = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.startLineNumber, selection.startColumn)
} else {
range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
}
editor.executeEdits('insert-fn', [{ range, text: insertText, forceMoveMarkers: true }])
const newVal = editor.getModel().getValue()
onChange(newVal)
return
}
if (mode === 'prepend') {
onChange(insertText + value)
} else {
onChange(value + insertText)
}
} catch (err) {
// eslint-disable-next-line no-console
console.error('Insert function call failed', err)
}
insertAt(`\${${sourceNode.id}()}`, mode)
},
[onChange, value]
[insertAt]
)
const extensions = useMemo(() => [yaml()], [])
return (
<BaseNode className="w-80">
<BaseNodeHeader className="border-b">
@@ -295,24 +193,16 @@ export const ConfigNode = memo(function ConfigNode({ id, data }: Props) {
</div>
)}
<div style={{ height: 180 }} className="w-full nodrag nopan">
<Editor
height="100%"
defaultLanguage="yaml"
<div style={{ height: 180 }} className="w-full nodrag nopan overflow-hidden rounded border border-input">
<CodeMirror
// @ts-expect-error ref is { view, state, editor }; package ref type not in our node_modules
ref={editorRef}
value={value}
theme="vs-light"
onMount={handleEditorMount}
height="180px"
extensions={extensions}
onChange={onChange}
options={{
minimap: { enabled: false },
fontSize: 12,
lineHeight: 16,
glyphMargin: false,
renderLineHighlight: 'none',
lineDecorationsWidth: 2,
padding: { top: 0, bottom: 4 },
lineNumbers: 'on',
}}
basicSetup={{ lineNumbers: true, foldGutter: false }}
className="text-sm [&_.cm-editor]:outline-none [&_.cm-editor]:cursor-text [&_.cm-gutters]:border-0"
/>
</div>
</BaseNodeContent>

View File

@@ -1,5 +1,6 @@
import React, { memo, useCallback, useContext, useEffect, useRef, useState } from 'react'
import Editor, { loader } from '@monaco-editor/react'
import React, { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import CodeMirror from '@uiw/react-codemirror'
import { javascript } from '@codemirror/lang-javascript'
import FlowContext from '../../lib/flowContext'
import {
BaseNode,
@@ -11,8 +12,6 @@ import {
import { InputHandle, OutputHandle } from './NodeHandles'
import { Code2 } from 'lucide-react'
loader.config({ paths: { vs: 'https://unpkg.com/monaco-editor@latest/min/vs' } })
type Props = {
id: string
data: { body?: string }
@@ -26,8 +25,6 @@ export const FunctionNode = memo(function FunctionNode({ id, data }: Props) {
const [value, setValue] = useState<string>(data?.body ?? DEFAULT_BODY)
const ctx = useContext(FlowContext)
const setNodes = ctx?.setNodes
const editorRef = useRef<any>(null)
const monacoRef = useRef<any>(null)
useEffect(() => {
if (setNodes) {
@@ -39,29 +36,21 @@ export const FunctionNode = memo(function FunctionNode({ id, data }: Props) {
}, [])
const onChange = useCallback(
(val?: string) => {
const v = val ?? ''
setValue(v)
(val: string) => {
setValue(val)
if (setNodes) {
setNodes((nds: any[]) =>
nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, body: v } } : n))
nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, body: val } } : n))
)
}
},
[id, setNodes]
)
const handleEditorMount = useCallback((editor: any, monaco: any) => {
editorRef.current = editor
monacoRef.current = monaco
try {
editor.onKeyDown((e: any) => e?.browserEvent?.stopPropagation())
editor.onMouseDown((e: any) => e?.event?.stopPropagation())
} catch {}
}, [])
const storedBody = ctx?.nodes?.find((n: any) => n.id === id)?.data?.body ?? ''
const extensions = useMemo(() => [javascript()], [])
return (
<BaseNode className="w-72">
<BaseNodeHeader className="border-b">
@@ -73,23 +62,14 @@ export const FunctionNode = memo(function FunctionNode({ id, data }: Props) {
<p className="text-[10px] text-muted-foreground mb-1">
Call in config: <code className="rounded bg-muted px-0.5">$&#123;{id}(var1, var2)&#125;</code>
</p>
<div style={{ height: 120 }} className="w-full nodrag nopan rounded border border-input overflow-hidden">
<Editor
height="100%"
defaultLanguage="javascript"
<div style={{ height: 120 }} className="w-full nodrag nopan overflow-hidden rounded border border-input">
<CodeMirror
value={value}
theme="vs-light"
onMount={handleEditorMount}
height="120px"
extensions={extensions}
onChange={onChange}
options={{
minimap: { enabled: false },
fontSize: 11,
lineHeight: 16,
lineNumbers: 'on',
lineDecorationsWidth: 2,
scrollBeyondLastLine: false,
padding: { top: 4, bottom: 4 },
}}
basicSetup={{ lineNumbers: true, foldGutter: false }}
className="text-xs [&_.cm-editor]:outline-none [&_.cm-editor]:cursor-text [&_.cm-gutters]:border-0"
/>
</div>
</BaseNodeContent>