replace monaco with codemirror
This commit is contained in:
@@ -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()
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
// ignore if Monaco internals change
|
||||
}
|
||||
}, [])
|
||||
view.dispatch({ changes: { from, to: from, insert: insertText } })
|
||||
const newVal = view.state.doc.toString()
|
||||
onChange(newVal)
|
||||
return
|
||||
}
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user