improve menus
This commit is contained in:
61
src/App.tsx
61
src/App.tsx
@@ -23,13 +23,15 @@ import { useTheme } from './lib/themeContext'
|
|||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuGroup,
|
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuLabel,
|
ContextMenuSub,
|
||||||
|
ContextMenuSubContent,
|
||||||
|
ContextMenuSubTrigger,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
|
ContextMenuGroup
|
||||||
} from "@/components/ui/context-menu"
|
} from "@/components/ui/context-menu"
|
||||||
import { Button } from '@/components/ui/button'
|
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
|
// Generate short unique node ids when missing
|
||||||
const genId = () => `node_${Math.random().toString(36).slice(2, 9)}`
|
const genId = () => `node_${Math.random().toString(36).slice(2, 9)}`
|
||||||
|
|
||||||
@@ -118,14 +120,13 @@ export default function App() {
|
|||||||
const click = contextTarget ?? lastClickRef.current
|
const click = contextTarget ?? lastClickRef.current
|
||||||
const clientX = click?.clientX ?? window.innerWidth / 2
|
const clientX = click?.clientX ?? window.innerWidth / 2
|
||||||
const clientY = click?.clientY ?? window.innerHeight / 2
|
const clientY = click?.clientY ?? window.innerHeight / 2
|
||||||
const rect = wrapperRef.current?.getBoundingClientRect()
|
let position: { x: number; y: number }
|
||||||
const point = rect ? { x: clientX - rect.left, y: clientY - rect.top } : { x: clientX, y: clientY }
|
|
||||||
let position = point
|
|
||||||
try {
|
try {
|
||||||
// project to flow coords when possible
|
// Convert screen (client) coordinates to flow coordinates — same position as where the context menu opened
|
||||||
position = rfInstance.project ? rfInstance.project(point) : point
|
const screenToFlow = rfInstance.screenToFlowPosition ?? rfInstance.project
|
||||||
} catch (e) {
|
position = screenToFlow.call(rfInstance, { x: clientX, y: clientY })
|
||||||
// ignore and use raw point
|
} catch {
|
||||||
|
position = { x: clientX, y: clientY }
|
||||||
}
|
}
|
||||||
position = snapToGrid(position.x, position.y)
|
position = snapToGrid(position.x, position.y)
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ export default function App() {
|
|||||||
lastClickRef.current = null
|
lastClickRef.current = null
|
||||||
setContextTarget(null)
|
setContextTarget(null)
|
||||||
},
|
},
|
||||||
[rfInstance, setNodes]
|
[rfInstance, setNodes, contextTarget]
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleteNode = React.useCallback((id: string | undefined) => {
|
const deleteNode = React.useCallback((id: string | undefined) => {
|
||||||
@@ -204,19 +205,37 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
|
|
||||||
<ContextMenuContent>
|
<ContextMenuContent className="w-48">
|
||||||
{contextTarget?.type === 'node' ? (
|
{contextTarget?.type === 'node' ? (
|
||||||
<ContextMenuItem onSelect={() => deleteNode(contextTarget.nodeId)}>Delete</ContextMenuItem>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ContextMenuGroup>
|
<ContextMenuGroup>
|
||||||
<ContextMenuLabel>New Node</ContextMenuLabel>
|
<ContextMenuItem onSelect={() => deleteNode(contextTarget.nodeId)}>Delete</ContextMenuItem>
|
||||||
<ContextMenuItem onSelect={() => createNode('config')}>Config</ContextMenuItem>
|
</ContextMenuGroup>
|
||||||
<ContextMenuItem onSelect={() => createNode('render')}>Renderer</ContextMenuItem>
|
) : (
|
||||||
<ContextMenuItem onSelect={() => createNode('variable')}>Variable</ContextMenuItem>
|
<ContextMenuGroup>
|
||||||
<ContextMenuItem onSelect={() => createNode('function')}>Function</ContextMenuItem>
|
<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>
|
</ContextMenuGroup>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
|
|||||||
@@ -44,11 +44,12 @@ body {
|
|||||||
|
|
||||||
@keyframes edge-flow {
|
@keyframes edge-flow {
|
||||||
from {
|
from {
|
||||||
stroke-dashoffset: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
stroke-dashoffset: 14;
|
stroke-dashoffset: 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Resize control: snappy drag, no transition or selection delay */
|
/* Resize control: snappy drag, no transition or selection delay */
|
||||||
|
|||||||
Reference in New Issue
Block a user