This commit is contained in:
2026-03-07 23:13:07 +01:00
parent 4c33d1095d
commit 640c8b7d98
6 changed files with 41 additions and 65 deletions

View File

@@ -80,11 +80,6 @@ export function BaseNode({
)} )}
style={{ margin: 0, touchAction: "none", userSelect: "none" }} style={{ margin: 0, touchAction: "none", userSelect: "none" }}
> >
<span
className="absolute bottom-0 right-0 w-8 h-8 rounded-br border-l border-t border-border/30 hover:border-border/50"
title="Drag corner to resize"
aria-label="Resize node"
/>
</NodeResizeControl> </NodeResizeControl>
)} )}
{handles} {handles}
@@ -113,23 +108,6 @@ export function BaseNodeHeader({
); );
} }
/**
* The title text for the node. To maintain a native application feel, the title
* text is not selectable.
*/
export function BaseNodeHeaderTitle({
className,
...props
}: ComponentProps<"h3">) {
return (
<h3
data-slot="base-node-title"
className={cn("user-select-none flex-1 font-semibold", className)}
{...props}
/>
);
}
/** /**
* Standard node header: icon on the left, title taking the rest. Use for all node types. * Standard node header: icon on the left, title taking the rest. Use for all node types.
* Inlined so it does not depend on BaseNodeHeader/BaseNodeHeaderTitle (avoids reference errors with HMR). * Inlined so it does not depend on BaseNodeHeader/BaseNodeHeaderTitle (avoids reference errors with HMR).
@@ -153,7 +131,7 @@ export function BaseNodeHeaderRow({
{icon} {icon}
<h3 <h3
data-slot="base-node-title" data-slot="base-node-title"
className="user-select-none flex-1 font-semibold" className="user-select-none flex-1 font-mono"
> >
{title} {title}
</h3> </h3>

View File

@@ -162,7 +162,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
return ( return (
<BaseNode className="min-w-80 min-h-[280px]" dimensions={dimensions} resizable nodeId={id} handles={<><InputHandle id="ain" /><OutputHandle id="out" /></>}> <BaseNode className="min-w-80 min-h-[280px]" dimensions={dimensions} resizable nodeId={id} handles={<><InputHandle id="ain" /><OutputHandle id="out" /></>}>
<BaseNodeHeaderRow icon={<ScrollText className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={`${id}.puml`} />} /> <BaseNodeHeaderRow icon={<ScrollText className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={`${id}`} />} />
<BaseNodeContent> <BaseNodeContent>
<div className="shrink-0 w-full"> <div className="shrink-0 w-full">
@@ -248,7 +248,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
/> />
</div> </div>
<div ref={editorContainerRef} className="min-h-0 flex-1 w-full nodrag nopan overflow-hidden rounded border border-input"> <div ref={editorContainerRef} className="min-h-0 flex-1 w-full nodrag nopan overflow-hidden border-t border-input">
<CodeMirror <CodeMirror
// @ts-expect-error ref is { view, state, editor }; package ref type not in our node_modules // @ts-expect-error ref is { view, state, editor }; package ref type not in our node_modules
ref={editorRef} ref={editorRef}

View File

@@ -52,13 +52,10 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height
<BaseNodeHeaderRow icon={<Code2 className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={id} />} /> <BaseNodeHeaderRow icon={<Code2 className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={id} />} />
<BaseNodeContent> <BaseNodeContent>
<div className="shrink-0 w-full mb-1"> <div className="shrink-0 w-full">
<NodeMenubar nodeId={id} nodeType="function" /> <NodeMenubar nodeId={id} nodeType="function" />
</div> </div>
<p className="shrink-0 text-[10px] text-muted-foreground mb-1"> <div ref={editorContainerRef} className="min-h-0 flex-1 w-full nodrag nopan overflow-hidden border-t border-input">
Call in config: <code className="rounded bg-muted px-0.5">$&#123;{id}(var1, var2)&#125;</code>
</p>
<div ref={editorContainerRef} className="min-h-0 flex-1 w-full nodrag nopan overflow-hidden rounded border border-input">
<CodeMirror <CodeMirror
value={bodyValue} value={bodyValue}
height={`${editorHeight}px`} height={`${editorHeight}px`}
@@ -72,7 +69,7 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height
</BaseNodeContent> </BaseNodeContent>
<BaseNodeFooter> <BaseNodeFooter>
<BaseNodeFooterText>Body: {bodyValue ? `${bodyValue.length} chars` : 'none'}</BaseNodeFooterText> <BaseNodeFooterText>{bodyValue ? `${bodyValue.length} chars` : 'none'}</BaseNodeFooterText>
</BaseNodeFooter> </BaseNodeFooter>
</BaseNode> </BaseNode>
) )

View File

@@ -57,7 +57,7 @@ export function NodeMenubar({ nodeId, nodeType, editInputsContent, insertTagsCon
const onCopy = useCallback(() => { const onCopy = useCallback(() => {
if (!node) return if (!node) return
const copy = { id: node.id, type: node.type, data: node.data, position: node.position, style: node.style } const copy = { id: node.id, type: node.type, data: node.data, position: node.position, style: node.style }
navigator.clipboard?.writeText(JSON.stringify(copy)).catch(() => {}) navigator.clipboard?.writeText(JSON.stringify(copy)).catch(() => { })
}, [node]) }, [node])
const onReset = useCallback(() => { const onReset = useCallback(() => {
@@ -79,7 +79,7 @@ export function NodeMenubar({ nodeId, nodeType, editInputsContent, insertTagsCon
}, [nodeId, ctx]) }, [nodeId, ctx])
return ( return (
<Menubar className="h-auto min-h-0 flex items-center bg-none p-1 border-none shadow-none"> <Menubar className="h-auto min-h-0 flex items-center bg-none p-1 border-t-0 border-l-0 border-r-0 border-b shadow-none rounded-none text-muted-foreground">
<MenubarMenu> <MenubarMenu>
<MenubarTrigger className="px-1.5 py-0 text-xs"> <MenubarTrigger className="px-1.5 py-0 text-xs">
Node Node

View File

@@ -403,7 +403,7 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
<div className="flex min-h-0 flex-1 items-center justify-center text-xs text-muted-foreground">Rendering</div> <div className="flex min-h-0 flex-1 items-center justify-center text-xs text-muted-foreground">Rendering</div>
) : svgContent ? ( ) : svgContent ? (
<div <div
className="min-h-0 flex-1 w-full overflow-auto rounded border border-input bg-white dark:bg-gray-900 [&_svg]:max-w-full [&_svg]:h-auto" className="min-h-0 flex-1 w-full overflow-auto bg-white dark:bg-secondary [&_svg]:max-w-full [&_svg]:h-auto"
dangerouslySetInnerHTML={{ __html: svgContent }} dangerouslySetInnerHTML={{ __html: svgContent }}
/> />
) : null} ) : null}

View File

@@ -94,46 +94,47 @@ export const VariableNode = memo(function VariableNode({ id, data }: Props) {
<BaseNode className="min-w-56 min-h-[180px]" handles={<OutputHandle id="out" />}> <BaseNode className="min-w-56 min-h-[180px]" handles={<OutputHandle id="out" />}>
<BaseNodeHeaderRow icon={<Variable className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={id} />} /> <BaseNodeHeaderRow icon={<Variable className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={id} />} />
<BaseNodeContent className="gap-2 p-3"> <BaseNodeContent>
<div className="shrink-0 w-full"> <div className="shrink-0 w-full">
<NodeMenubar nodeId={id} nodeType="variable" /> <NodeMenubar nodeId={id} nodeType="variable" />
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col p-2 gap-2">
<label className="text-xs font-medium text-muted-foreground">Type</label> <div className="flex flex-col gap-1.5">
<Select value={valueType} onValueChange={onTypeChange}> <label className="text-xs font-medium text-muted-foreground">Type</label>
<SelectTrigger> <Select value={valueType} onValueChange={onTypeChange}>
<SelectValue placeholder="Type" /> <SelectTrigger>
</SelectTrigger> <SelectValue placeholder="Type" />
<SelectContent> </SelectTrigger>
<SelectItem value="string">String</SelectItem> <SelectContent>
<SelectItem value="number">Number</SelectItem> <SelectItem value="string">String</SelectItem>
<SelectItem value="boolean">Boolean</SelectItem> <SelectItem value="number">Number</SelectItem>
</SelectContent> <SelectItem value="boolean">Boolean</SelectItem>
</Select> </SelectContent>
</div> </Select>
<div className="flex flex-col gap-1.5"> </div>
<label className="text-xs font-medium text-muted-foreground">Value</label> <div className="flex flex-col gap-1.5">
{valueType === 'boolean' ? ( <label className="text-xs font-medium text-muted-foreground">Value</label>
<div className="flex items-center gap-2 text-sm"> {valueType === 'boolean' ? (
<Switch <div className="flex items-center gap-2 text-sm">
checked={value === true} <Switch
onCheckedChange={onBooleanChange} checked={value === true}
onCheckedChange={onBooleanChange}
/>
<span className="text-muted-foreground">{value ? 'true' : 'false'}</span>
</div>
) : (
<Input
type={valueType === 'number' ? 'number' : 'text'}
value={valueType === 'number' ? (value as number) : displayValue}
onChange={onValueChange}
/> />
<span className="text-muted-foreground">{value ? 'true' : 'false'}</span> )}
</div> </div>
) : (
<Input
type={valueType === 'number' ? 'number' : 'text'}
value={valueType === 'number' ? (value as number) : displayValue}
onChange={onValueChange}
/>
)}
</div> </div>
</BaseNodeContent> </BaseNodeContent>
<BaseNodeFooter> <BaseNodeFooter>
<BaseNodeFooterText> <BaseNodeFooterText>
Use in config: <code className="rounded bg-muted px-0.5">prop: {'${'}{id}{'}'}</code>
</BaseNodeFooterText> </BaseNodeFooterText>
</BaseNodeFooter> </BaseNodeFooter>
</BaseNode> </BaseNode>