diff --git a/package-lock.json b/package-lock.json index 80d587d..d0993f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "dependencies": { "@monaco-editor/react": "^4.7.0", "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-menubar": "^1.1.16", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", "@xyflow/react": "^12.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1556,6 +1559,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1754,6 +1775,56 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -1857,6 +1928,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -1888,10 +1977,56 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" diff --git a/package.json b/package.json index 2436508..33762b9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "dependencies": { "@monaco-editor/react": "^4.7.0", "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-menubar": "^1.1.16", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", "@xyflow/react": "^12.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/components/graph/BaseNode.tsx b/src/components/graph/BaseNode.tsx index 10240b3..e276b76 100644 --- a/src/components/graph/BaseNode.tsx +++ b/src/components/graph/BaseNode.tsx @@ -68,7 +68,7 @@ export function BaseNodeContent({ return (
); diff --git a/src/components/graph/ConfigNode.tsx b/src/components/graph/ConfigNode.tsx index 37e2531..d6373a4 100644 --- a/src/components/graph/ConfigNode.tsx +++ b/src/components/graph/ConfigNode.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' +import React, { memo, useCallback, useContext, useEffect, useRef, useState } from 'react' import { Handle, Position } from 'reactflow' import Editor, { loader } from '@monaco-editor/react' import FlowContext from '../../lib/flowContext' @@ -9,7 +9,19 @@ import { BaseNodeHeader, BaseNodeHeaderTitle, } from './BaseNode' -import { Pencil } from 'lucide-react' +import { GitBranch, Pencil } from 'lucide-react' +import { + Menubar, + MenubarContent, + MenubarItem, + MenubarLabel, + MenubarMenu, + MenubarSeparator, + MenubarSub, + MenubarSubContent, + MenubarSubTrigger, + MenubarTrigger, +} from '../ui/menubar' // 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' } }) @@ -59,7 +71,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data }: Props) { }, []) const insertIncludeFromNode = useCallback( - (sourceNode: any) => { + (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => { const ref = `${sourceNode.id}.yaml` const includeText = `!include ${ref}\n` const editor = editorRef.current @@ -67,22 +79,32 @@ export const ConfigNode = memo(function ConfigNode({ id, data }: Props) { try { if (editor && monaco) { + const model = editor.getModel() + const lineCount = model.getLineCount() const selection = editor.getSelection() + let range - if (selection) { + 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 { - const model = editor.getModel() - const lineCount = model.getLineCount() 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 } - onChange(value + '\n' + includeText) + 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) @@ -100,23 +122,43 @@ export const ConfigNode = memo(function ConfigNode({ id, data }: Props) {