contex improvements
This commit is contained in:
@@ -32,13 +32,13 @@ export function BaseNode({
|
||||
// and would be clipped. The inner content wrapper has overflow-hidden for scrolling.
|
||||
const appliedStyle = hasSize
|
||||
? {
|
||||
...style,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
display: "flex" as const,
|
||||
flexDirection: "column" as const,
|
||||
contain: "layout" as const,
|
||||
}
|
||||
...style,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
display: "flex" as const,
|
||||
flexDirection: "column" as const,
|
||||
contain: "layout" as const,
|
||||
}
|
||||
: style
|
||||
? { ...style, display: "flex", flexDirection: "column" as const }
|
||||
: undefined;
|
||||
@@ -184,7 +184,7 @@ export function BaseNodeContent({
|
||||
return (
|
||||
<div
|
||||
data-slot="base-node-content"
|
||||
className={cn("min-h-0 flex-1 flex flex-col gap-y-2 overflow-auto pt-1", className)}
|
||||
className={cn("min-h-0 flex-1 flex flex-col overflow-auto pt-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -13,18 +13,15 @@ import {
|
||||
BaseNodeFooterText,
|
||||
BaseNodeHeaderRow,
|
||||
} from './BaseNode'
|
||||
import { GitBranchPlus, ScrollText } from 'lucide-react'
|
||||
import { ScrollText } from 'lucide-react'
|
||||
import {
|
||||
Menubar,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from '../ui/menubar'
|
||||
import { InputHandle, OutputHandle } from './NodeHandles'
|
||||
import { NodeMenubar } from './NodeMenubar'
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
@@ -159,14 +156,13 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
|
||||
<BaseNodeHeaderRow icon={<ScrollText className="size-4" />} title={`${id}.puml`} />
|
||||
|
||||
<BaseNodeContent>
|
||||
{hasDependencies && (
|
||||
<div className="shrink-0 w-full">
|
||||
<Menubar className="h-auto bg-none p-1 border-none shadow-none">
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger className="px-1.5 py-0 text-xs">
|
||||
<GitBranchPlus className="size-3.5" />
|
||||
</MenubarTrigger>
|
||||
<MenubarContent className="min-w-[12rem]">
|
||||
<div className="shrink-0 w-full">
|
||||
<NodeMenubar
|
||||
nodeId={id}
|
||||
nodeType="config"
|
||||
editInputsContent={
|
||||
hasDependencies ? (
|
||||
<>
|
||||
{connectedConfigNodes.map((n: any) => (
|
||||
<MenubarSub key={`config-${n.id}`}>
|
||||
<MenubarSubTrigger className="text-xs">
|
||||
@@ -224,11 +220,11 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
))}
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div ref={editorContainerRef} className="min-h-0 flex-1 w-full nodrag nopan overflow-hidden rounded border border-input">
|
||||
<CodeMirror
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
BaseNodeHeaderRow,
|
||||
} from './BaseNode'
|
||||
import { InputHandle, OutputHandle } from './NodeHandles'
|
||||
import { NodeMenubar } from './NodeMenubar'
|
||||
import { Code2 } from 'lucide-react'
|
||||
|
||||
type Props = {
|
||||
@@ -66,6 +67,9 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height
|
||||
<BaseNodeHeaderRow icon={<Code2 className="size-4" />} title={id} />
|
||||
|
||||
<BaseNodeContent>
|
||||
<div className="shrink-0 w-full mb-1">
|
||||
<NodeMenubar nodeId={id} nodeType="function" />
|
||||
</div>
|
||||
<p className="shrink-0 text-[10px] text-muted-foreground mb-1">
|
||||
Call in config: <code className="rounded bg-muted px-0.5">${{id}(var1, var2)}</code>
|
||||
</p>
|
||||
|
||||
112
src/components/graph/NodeMenubar.tsx
Normal file
112
src/components/graph/NodeMenubar.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useCallback, useContext } from 'react'
|
||||
import FlowContext from '../../lib/flowContext'
|
||||
import { DEFAULT_NODE_STYLE, genId, getDefaultDataForType } from '../../lib/flowUtils'
|
||||
import {
|
||||
Menubar,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from '../ui/menubar'
|
||||
|
||||
const DUPLICATE_OFFSET = { x: 30, y: 30 }
|
||||
|
||||
type NodeType = 'config' | 'render' | 'variable' | 'function'
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
nodeType: NodeType
|
||||
/** Content for Edit → Inputs (config and function nodes only) */
|
||||
editInputsContent?: React.ReactNode
|
||||
}
|
||||
|
||||
export function NodeMenubar({ nodeId, nodeType, editInputsContent }: Props) {
|
||||
const ctx = useContext(FlowContext)
|
||||
const nodes = ctx?.nodes ?? []
|
||||
const setNodes = ctx?.setNodes
|
||||
const setEdges = ctx?.setEdges
|
||||
|
||||
const edges = ctx?.edges ?? []
|
||||
const node = nodes.find((n: any) => n.id === nodeId)
|
||||
const hasEdit = nodeType === 'config' || nodeType === 'function'
|
||||
const hasConnectedNodes = edges.some((e: any) => e.target === nodeId)
|
||||
|
||||
const onDuplicate = useCallback(() => {
|
||||
if (!setNodes || !node) return
|
||||
const newId = genId()
|
||||
const pos = node.position ?? { x: 0, y: 0 }
|
||||
const newNode = {
|
||||
id: newId,
|
||||
type: node.type,
|
||||
position: { x: pos.x + DUPLICATE_OFFSET.x, y: pos.y + DUPLICATE_OFFSET.y },
|
||||
data: typeof node.data === 'object' && node.data !== null ? { ...node.data } : node.data,
|
||||
style: DEFAULT_NODE_STYLE[nodeType] ?? DEFAULT_NODE_STYLE.config,
|
||||
}
|
||||
if (newNode.data?.title && nodeType === 'config') newNode.data.title = `config-${newId}`
|
||||
setNodes((nds: any[]) => nds.concat(newNode))
|
||||
}, [node, nodeType, setNodes])
|
||||
|
||||
const onCopy = useCallback(() => {
|
||||
if (!node) return
|
||||
const copy = { id: node.id, type: node.type, data: node.data, position: node.position, style: node.style }
|
||||
navigator.clipboard?.writeText(JSON.stringify(copy)).catch(() => {})
|
||||
}, [node])
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
if (!setNodes) return
|
||||
const defaultData = getDefaultDataForType(nodeType, nodeId)
|
||||
setNodes((nds: any[]) =>
|
||||
nds.map((n) => (n.id === nodeId ? { ...n, data: defaultData } : n))
|
||||
)
|
||||
}, [nodeId, nodeType, setNodes])
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
if (!setNodes || !setEdges) return
|
||||
setNodes((nds: any[]) => nds.filter((n: any) => n.id !== nodeId))
|
||||
setEdges((eds: any[]) => eds.filter((e: any) => e.source !== nodeId && e.target !== nodeId))
|
||||
}, [nodeId, setNodes, setEdges])
|
||||
|
||||
return (
|
||||
<Menubar className="h-auto min-h-0 flex items-center bg-none p-1 border-none shadow-none">
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger className="px-1.5 py-0 text-xs">
|
||||
File
|
||||
</MenubarTrigger>
|
||||
<MenubarContent className="min-w-[10rem]">
|
||||
<MenubarItem className="text-xs" onClick={onDuplicate}>
|
||||
Duplicate
|
||||
</MenubarItem>
|
||||
<MenubarItem className="text-xs" onClick={onCopy}>
|
||||
Copy
|
||||
</MenubarItem>
|
||||
<MenubarItem className="text-xs" onClick={onReset}>
|
||||
Reset
|
||||
</MenubarItem>
|
||||
<MenubarItem className="text-xs text-destructive focus:text-destructive" onClick={onDelete}>
|
||||
Delete
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
{hasEdit && (
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger className="px-1.5 py-0 text-xs">
|
||||
Edit
|
||||
</MenubarTrigger>
|
||||
<MenubarContent className="min-w-[10rem]">
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger className="text-xs" disabled={!hasConnectedNodes}>
|
||||
Inputs
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent className="min-w-[12rem]">
|
||||
{editInputsContent}
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
)}
|
||||
</Menubar>
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
BaseNodeFooterText,
|
||||
BaseNodeHeaderRow,
|
||||
} from './BaseNode'
|
||||
import { NodeMenubar } from './NodeMenubar'
|
||||
import { Sparkles } from 'lucide-react'
|
||||
import { InputHandle, OutputHandle } from './NodeHandles'
|
||||
|
||||
@@ -297,6 +298,9 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
|
||||
<BaseNodeHeaderRow icon={<Sparkles className="size-4" />} title={id} />
|
||||
|
||||
<BaseNodeContent>
|
||||
<div className="shrink-0 w-full">
|
||||
<NodeMenubar nodeId={id} nodeType="render" />
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 flex flex-col">
|
||||
{incomingIds.length === 0 ? (
|
||||
<Empty className="min-h-0 flex-1">
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
BaseNodeFooterText,
|
||||
BaseNodeHeaderRow,
|
||||
} from './BaseNode'
|
||||
import { NodeMenubar } from './NodeMenubar'
|
||||
import { Input } from '../ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
||||
import { Switch } from '../ui/switch'
|
||||
@@ -93,6 +94,9 @@ export const VariableNode = memo(function VariableNode({ id, data }: Props) {
|
||||
<BaseNodeHeaderRow icon={<Variable className="size-4" />} title={id} />
|
||||
|
||||
<BaseNodeContent className="gap-2 p-3">
|
||||
<div className="shrink-0 w-full">
|
||||
<NodeMenubar nodeId={id} nodeType="variable" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground">Type</label>
|
||||
<Select value={valueType} onValueChange={onTypeChange}>
|
||||
|
||||
@@ -73,7 +73,7 @@ const MenubarSubTrigger = React.forwardRef<
|
||||
<MenubarPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user