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  }