improve menus
This commit is contained in:
101
src/App.tsx
101
src/App.tsx
@@ -23,13 +23,15 @@ import { useTheme } from './lib/themeContext'
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuGroup,
|
||||
ContextMenuItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuGroup
|
||||
} from "@/components/ui/context-menu"
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Moon, Sun } from 'lucide-react'
|
||||
import { Code2, Moon, ScrollText, Sparkles, Sun, Variable } from 'lucide-react'
|
||||
// Generate short unique node ids when missing
|
||||
const genId = () => `node_${Math.random().toString(36).slice(2, 9)}`
|
||||
|
||||
@@ -118,14 +120,13 @@ export default function App() {
|
||||
const click = contextTarget ?? lastClickRef.current
|
||||
const clientX = click?.clientX ?? window.innerWidth / 2
|
||||
const clientY = click?.clientY ?? window.innerHeight / 2
|
||||
const rect = wrapperRef.current?.getBoundingClientRect()
|
||||
const point = rect ? { x: clientX - rect.left, y: clientY - rect.top } : { x: clientX, y: clientY }
|
||||
let position = point
|
||||
let position: { x: number; y: number }
|
||||
try {
|
||||
// project to flow coords when possible
|
||||
position = rfInstance.project ? rfInstance.project(point) : point
|
||||
} catch (e) {
|
||||
// ignore and use raw point
|
||||
// Convert screen (client) coordinates to flow coordinates — same position as where the context menu opened
|
||||
const screenToFlow = rfInstance.screenToFlowPosition ?? rfInstance.project
|
||||
position = screenToFlow.call(rfInstance, { x: clientX, y: clientY })
|
||||
} catch {
|
||||
position = { x: clientX, y: clientY }
|
||||
}
|
||||
position = snapToGrid(position.x, position.y)
|
||||
|
||||
@@ -154,7 +155,7 @@ export default function App() {
|
||||
lastClickRef.current = null
|
||||
setContextTarget(null)
|
||||
},
|
||||
[rfInstance, setNodes]
|
||||
[rfInstance, setNodes, contextTarget]
|
||||
)
|
||||
|
||||
const deleteNode = React.useCallback((id: string | undefined) => {
|
||||
@@ -182,46 +183,64 @@ export default function App() {
|
||||
<div style={{ width: '100%', height: '100%' }}>
|
||||
<ReactFlowProvider initialNodes={nodes} initialEdges={edges} fitView>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
defaultEdgeOptions={{ type: 'animated' }}
|
||||
colorMode={theme as ColorMode}
|
||||
snapToGrid
|
||||
snapGrid={SNAP_GRID}
|
||||
fitView
|
||||
onInit={onInit}
|
||||
>
|
||||
<Background variant="dots" gap={20} />
|
||||
<Controls />
|
||||
<MiniMap />
|
||||
</ReactFlow>
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
defaultEdgeOptions={{ type: 'animated' }}
|
||||
colorMode={theme as ColorMode}
|
||||
snapToGrid
|
||||
snapGrid={SNAP_GRID}
|
||||
fitView
|
||||
onInit={onInit}
|
||||
>
|
||||
<Background variant="dots" gap={20} />
|
||||
<Controls />
|
||||
<MiniMap />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
|
||||
<ContextMenuContent>
|
||||
<ContextMenuContent className="w-48">
|
||||
{contextTarget?.type === 'node' ? (
|
||||
<ContextMenuItem onSelect={() => deleteNode(contextTarget.nodeId)}>Delete</ContextMenuItem>
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuItem onSelect={() => deleteNode(contextTarget.nodeId)}>Delete</ContextMenuItem>
|
||||
</ContextMenuGroup>
|
||||
) : (
|
||||
<>
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuLabel>New Node</ContextMenuLabel>
|
||||
<ContextMenuItem onSelect={() => createNode('config')}>Config</ContextMenuItem>
|
||||
<ContextMenuItem onSelect={() => createNode('render')}>Renderer</ContextMenuItem>
|
||||
<ContextMenuItem onSelect={() => createNode('variable')}>Variable</ContextMenuItem>
|
||||
<ContextMenuItem onSelect={() => createNode('function')}>Function</ContextMenuItem>
|
||||
</ContextMenuGroup>
|
||||
</>
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger>Create node</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent className="w-44">
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuItem onSelect={() => createNode('config')}>
|
||||
<ScrollText className="mr-2 h-4 w-4" />
|
||||
Config
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onSelect={() => createNode('render')}>
|
||||
<Sparkles className="mr-2 h-4 w-4" />
|
||||
Renderer
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onSelect={() => createNode('variable')}>
|
||||
<Variable className="mr-2 h-4 w-4" />
|
||||
Variable
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onSelect={() => createNode('function')}>
|
||||
<Code2 className="mr-2 h-4 w-4" />
|
||||
Function
|
||||
</ContextMenuItem>
|
||||
</ContextMenuGroup>
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
</ContextMenuGroup>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</FlowContext.Provider>
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,12 @@ body {
|
||||
|
||||
@keyframes edge-flow {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: 14;
|
||||
}
|
||||
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resize control: snappy drag, no transition or selection delay */
|
||||
|
||||
Reference in New Issue
Block a user