go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/static/_nextjs/src/components/dialogLink.tsx (about) 1 /** 2 * Copyright (c) 2024 - Present. Will Charczuk. All rights reserved. 3 * Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 4 */ 5 import { Button, Dialog, DialogBody, DialogFooter, FormGroup, Intent, Tooltip, getKeyCombo } from "@blueprintjs/core"; 6 import * as api from "../api/nodes"; 7 import { MODIFIER_BIT_MASKS } from "@blueprintjs/core/lib/esm/components/hotkeys/hotkeyParser"; 8 import { useState } from "react"; 9 import { Connection, Node as FlowNode } from 'reactflow'; 10 import { SelectNode } from "./selectNode"; 11 import { nodeTypes } from "../refdata/nodeTypes"; 12 import { SelectStrings } from "./selectStrings"; 13 import { NodeData } from "../store/nodeData"; 14 15 export interface DialogLinkProps { 16 isOpen: boolean; 17 nodes: FlowNode<NodeData>[]; 18 onClose: () => void; 19 onLink: (connection: Connection) => void; 20 } 21 22 export function DialogLink(props: DialogLinkProps) { 23 const [sourceNode, setSourceNode] = useState<api.Node | undefined>(); 24 const [targetNode, setTargetNode] = useState<api.Node | undefined>(); 25 const [targetHandles, setTargetHandles] = useState<string[] | undefined>(); 26 const [targetHandle, setTargetHandle] = useState<string | null>(null); 27 28 const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => { 29 const combo = getKeyCombo(e.nativeEvent); 30 if (combo.key === "enter" && combo.modifiers === MODIFIER_BIT_MASKS['shift']) { 31 e.preventDefault(); 32 e.stopPropagation(); 33 if (sourceNode && targetNode) { 34 props.onLink({ source: sourceNode.id, sourceHandle: null, target: targetNode.id, targetHandle: targetHandle }); 35 } 36 } 37 } 38 39 const onSelectTarget = (t: api.Node) => { 40 setTargetNode(t) 41 const nt = nodeTypes[t.metadata.node_type]; 42 if (nt.inputs && nt.inputs.length > 0) { 43 setTargetHandles(nt.inputs.map(h => h.name || '')); 44 } 45 } 46 47 const doLink = () => { 48 if (sourceNode && targetNode) { 49 props.onLink({ source: sourceNode.id, sourceHandle: null, target: targetNode.id, targetHandle: targetHandle }); 50 } 51 } 52 53 return (<div onKeyDown={handleKeyDown}> 54 <Dialog 55 title="Add Node" 56 icon="add" 57 autoFocus={true} 58 enforceFocus={true} 59 canEscapeKeyClose={true} 60 isOpen={props.isOpen} 61 onClose={props.onClose} 62 isCloseButtonShown={true} 63 transitionDuration={100} 64 style={{ width: 'max-content' }} 65 > 66 <DialogBody> 67 <FormGroup label="Source Node" labelFor="source-node" inline={true}> 68 <SelectNode autoFocus={true} tabIndex={0} id="source-node" items={props.nodes} value={sourceNode?.id} onItemSelect={(n) => { setSourceNode(n) }} fill={true} /> 69 </FormGroup> 70 <FormGroup label="Target Node" labelFor="target-node" inline={true}> 71 <SelectNode autoFocus={false} tabIndex={0} id="target-node" items={props.nodes} value={targetNode?.id} onItemSelect={onSelectTarget} fill={true} /> 72 </FormGroup> 73 {targetHandles && targetHandles.length > 1 && ( 74 <FormGroup label="Target Node Handle" labelFor="target-handle" inline={true}> 75 <SelectStrings autoFocus={false} tabIndex={0} id="target-handle" items={targetHandles} value={targetHandle === null ? undefined : targetHandle} onItemSelect={(s) => setTargetHandle(s)} fill={true} /> 76 </FormGroup> 77 )} 78 </DialogBody> 79 <DialogFooter actions={<> 80 <Tooltip content="Close the dialog and cancel creating a node"> 81 <Button autoFocus={false} tabIndex={0} onClick={props.onClose} icon="cross">Cancel</Button> 82 </Tooltip> 83 <Tooltip intent={Intent.PRIMARY} content={<div><p>Create a new node</p><p>You can also use <code>shift+enter</code> to submit the form.</p></div>}> 84 <Button autoFocus={false} tabIndex={0} intent={Intent.PRIMARY} onClick={doLink} icon="floppy-disk">Save</Button> 85 </Tooltip> 86 </> 87 }> 88 </DialogFooter> 89 </Dialog> 90 </div>) 91 }