From 74fe1bed33b63d5bc184580f16701e418b2475aa Mon Sep 17 00:00:00 2001 From: dtoro Date: Sun, 8 Mar 2026 22:27:59 +0100 Subject: [PATCH] improved menus --- src/components/graph/ConfigNode.tsx | 137 +++++++++++++++---------- src/components/graph/FunctionNode.tsx | 57 +++++----- src/components/graph/NodeMenubar.tsx | 55 +++++++--- src/components/graph/RenderingNode.tsx | 6 +- src/components/graph/VariableNode.tsx | 4 +- src/lib/configTypes.ts | 4 +- 6 files changed, 153 insertions(+), 110 deletions(-) diff --git a/src/components/graph/ConfigNode.tsx b/src/components/graph/ConfigNode.tsx index cf0c9eb..cbee766 100644 --- a/src/components/graph/ConfigNode.tsx +++ b/src/components/graph/ConfigNode.tsx @@ -26,10 +26,13 @@ import { import { Code2, ScrollText, Variable } from 'lucide-react' import { MenubarItem, + MenubarSeparator, + MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, } from '../ui/menubar' +import { Kbd } from '../ui/kbd' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' import { InputHandle, OutputHandle } from './NodeHandles' import { NodeFooterEdgeIndicators } from './NodeFooterEdgeIndicators' @@ -191,31 +194,55 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }: const [editorHeight, editorContainerRef] = useResizeHeight(180) const insertBlocksContent = useMemo(() => { - return configType.insertBlocks.map((block, idx) => - isGroup(block) ? ( - - {block.label} - - {block.items.map(({ label, snippet }) => ( - insertAt(snippet, 'cursor')} - > - {label} - - ))} - - - ) : ( + const templatingGroup = configType.insertBlocks.find( + (b): b is import('../../lib/configTypes').InsertBlockGroup => isGroup(b) && b.label === 'Templating' + ) + const typeBlocks = configType.insertBlocks.filter((b) => !(isGroup(b) && b.label === 'Templating')) + const insertShortcut = Insert + const typeItems = typeBlocks.flatMap((block) => + isGroup(block) + ? block.items.map(({ label, snippet }) => ( + insertAt(snippet, 'cursor')} + > + {label} + {insertShortcut} + + )) + : [ + insertAt(block.snippet, 'cursor')} + > + {block.label} + {insertShortcut} + , + ] + ) + const templatingItems = + templatingGroup?.items.map(({ label, snippet }) => ( insertAt(block.snippet, 'cursor')} + key={label} + className="text-xs flex items-center group" + onClick={() => insertAt(snippet, 'cursor')} > - {block.label} + {label} + Insert - ) + )) ?? [] + return ( + <> + {typeItems} + {templatingItems.length > 0 && ( + <> + + {templatingItems} + + )} + ) }, [configType.insertBlocks, insertAt]) @@ -250,7 +277,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }: {connectedConfigNodes.map((n: any) => ( @@ -261,63 +288,59 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }: insertExtendsFromNode(n, 'cursor')} > Extend + Insert insertIncludeFromNode(n, 'cursor')} > Include + Insert insertImportFromNode(n, 'cursor')} > Import + Insert ))} {connectedVariableNodes.map((n: any) => ( - - - - {n.id} - - - insertVariableReference(n, 'cursor')} - > - Insert - - - + insertVariableReference(n, 'cursor')} + > + + {n.id} + Insert + ))} {connectedFunctionNodes.map((n: any) => ( - - - - {n.id} - - - insertFunctionCall(n, 'cursor')} - > - Insert - - - + insertFunctionCall(n, 'cursor')} + > + + {n.id} + Insert + ))} - ) : undefined + ) : ( + Connect nodes to insert references + ) } + insertMenuLabel="Blocks" + insertContentDirect insertTagsContent={insertBlocksContent} - insertTagsLabel={configType.label} /> @@ -338,7 +361,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }: - {content ? `${content.length} chars` : 'none'} + {configType.label} · {content ? `${content.length} chars` : 'none'} diff --git a/src/components/graph/FunctionNode.tsx b/src/components/graph/FunctionNode.tsx index 75872c1..fd6ea8f 100644 --- a/src/components/graph/FunctionNode.tsx +++ b/src/components/graph/FunctionNode.tsx @@ -15,7 +15,8 @@ import { InputHandle, OutputHandle } from './NodeHandles' import { NodeFooterEdgeIndicators } from './NodeFooterEdgeIndicators' import { NodeHeaderTitle } from './NodeHeaderTitle' import { NodeMenubar } from './NodeMenubar' -import { MenubarItem, MenubarSub, MenubarSubContent, MenubarSubTrigger } from '../ui/menubar' +import { MenubarItem, MenubarShortcut } from '../ui/menubar' +import { Kbd } from '../ui/kbd' import { Code2, Variable } from 'lucide-react' type Props = { @@ -110,43 +111,35 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height {connectedVariableNodes.map((n: any) => ( - - - - {n.id} - - - insertVariableAtCursor(n)} - > - Insert at cursor - - - + insertVariableAtCursor(n)} + > + + {n.id} + Insert + ))} {connectedFunctionNodes.map((n: any) => ( - - - - {n.id} - - - insertFunctionAtCursor(n)} - > - Insert at cursor - - - + insertFunctionAtCursor(n)} + > + + {n.id} + Insert + ))} - ) : undefined + ) : ( + Connect nodes to insert at cursor + ) } /> @@ -167,7 +160,7 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height - {bodyValue ? `Javascript | ${bodyValue.length} chars` : 'none'} + {bodyValue ? `JavaScript · ${bodyValue.length} chars` : 'JavaScript · none'} diff --git a/src/components/graph/NodeMenubar.tsx b/src/components/graph/NodeMenubar.tsx index f26b2d5..70a586f 100644 --- a/src/components/graph/NodeMenubar.tsx +++ b/src/components/graph/NodeMenubar.tsx @@ -20,17 +20,23 @@ type NodeType = 'config' | 'render' | 'variable' | 'function' type Props = { nodeId: string nodeType: NodeType - /** Content for Insert → Inputs (config and function nodes only) */ + /** Content for Insert → Inputs (function nodes); when inputsMenuContent is set, Inputs is a separate menu and this is not used in Insert */ editInputsContent?: React.ReactNode + /** When set, Inputs is rendered as its own top-level menu (config nodes); Insert then only shows markup-specific options */ + inputsMenuContent?: React.ReactNode /** Content for Insert → [Blocks/Tags] (e.g. type-specific snippets, config nodes) */ insertTagsContent?: React.ReactNode - /** Label for Insert submenu that shows insertTagsContent (default "Tags") */ + /** Label for the Insert/Blocks menu trigger (default "Insert") */ + insertMenuLabel?: string + /** When true, render insertTagsContent directly in the menu (no submenu level). Use for flat Blocks list. */ + insertContentDirect?: boolean + /** Label for Insert submenu that shows insertTagsContent when not insertContentDirect (default "Tags") */ insertTagsLabel?: string /** Extra content in Node menu (e.g. Export submenu for render nodes), before the separator */ nodeMenuExtraContent?: React.ReactNode } -export function NodeMenubar({ nodeId, nodeType, editInputsContent, insertTagsContent, insertTagsLabel = 'Tags', nodeMenuExtraContent }: Props) { +export function NodeMenubar({ nodeId, nodeType, editInputsContent, inputsMenuContent, insertTagsContent, insertMenuLabel = 'Insert', insertContentDirect, insertTagsLabel = 'Tags', nodeMenuExtraContent }: Props) { const ctx = useContext(FlowContext) const nodes = ctx?.nodes ?? [] const setNodes = ctx?.setNodes @@ -108,30 +114,45 @@ export function NodeMenubar({ nodeId, nodeType, editInputsContent, insertTagsCon - {hasEdit && ( + {inputsMenuContent != null && ( + + + Inputs + + + {inputsMenuContent} + + + )} + {hasEdit && (insertTagsContent != null || (editInputsContent != null && inputsMenuContent == null)) && ( - Insert + {insertMenuLabel} - - - Inputs - - - {editInputsContent} - - - {insertTagsContent != null && ( + {inputsMenuContent == null && editInputsContent != null && ( - - {insertTagsLabel} + + Inputs - {insertTagsContent} + {editInputsContent} )} + {insertTagsContent != null && + (insertContentDirect ? ( + insertTagsContent + ) : ( + + + {insertTagsLabel} + + + {insertTagsContent} + + + ))} )} diff --git a/src/components/graph/RenderingNode.tsx b/src/components/graph/RenderingNode.tsx index a62588d..c104e75 100644 --- a/src/components/graph/RenderingNode.tsx +++ b/src/components/graph/RenderingNode.tsx @@ -599,7 +599,11 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }: - {renderedContent ? (isSvgOutput ? 'Diagram' : 'Markdown') : error ? 'Error' : '—'} + {renderedContent + ? `${isSvgOutput ? 'Diagram' : 'Markdown'} · ${renderedContent.length} chars` + : error + ? 'Error' + : '—'} diff --git a/src/components/graph/VariableNode.tsx b/src/components/graph/VariableNode.tsx index 419d02e..ee02d0d 100644 --- a/src/components/graph/VariableNode.tsx +++ b/src/components/graph/VariableNode.tsx @@ -135,7 +135,9 @@ export const VariableNode = memo(function VariableNode({ id, data }: Props) { - + + {`${valueType.charAt(0).toUpperCase() + valueType.slice(1)} · ${displayValue.length ? `${displayValue.length} chars` : 'none'}`} + ) diff --git a/src/lib/configTypes.ts b/src/lib/configTypes.ts index 10d727b..03f68ff 100644 --- a/src/lib/configTypes.ts +++ b/src/lib/configTypes.ts @@ -38,7 +38,7 @@ const PLANTUML_INSERT_BLOCKS: InsertBlockOrGroup[] = [ { label: 'Arrow', snippet: ' -> ' }, { label: 'Note', snippet: 'note right of ' }, { - label: 'Nunjucks', + label: 'Templating', items: [ { label: 'Variable {{ }}', snippet: '{{ }}' }, { label: 'if / endif', snippet: '{% if %}\n \n{% endif %}' }, @@ -64,7 +64,7 @@ const MARKDOWN_INSERT_BLOCKS: InsertBlockOrGroup[] = [ { label: 'List item', snippet: '- ' }, { label: 'Blockquote', snippet: '> ' }, { - label: 'Nunjucks', + label: 'Templating', items: [ { label: 'Variable {{ }}', snippet: '{{ }}' }, { label: 'if / endif', snippet: '{% if %}\n \n{% endif %}' },