go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/static/_nextjs/src/components/selectNode.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, MenuItem } from "@blueprintjs/core";
     6  import { ItemPredicate, ItemRenderer, Select } from "@blueprintjs/select";
     7  import { useEffect, useState } from "react";
     8  import { Node as FlowNode } from 'reactflow';
     9  import * as api from "../api/nodes";
    10  import { nodeTypes } from "../refdata/nodeTypes";
    11  import { NodeData } from "../store/nodeData";
    12  
    13  export interface SelectNodeProps {
    14    id?: string;
    15    items: FlowNode<NodeData>[];
    16    value?: string;
    17    disabled?: boolean;
    18    fill?: boolean;
    19    tabIndex?: number;
    20    autoFocus?: boolean;
    21    onItemSelect: (item: api.Node) => void;
    22  }
    23  
    24  export function SelectNode(props: SelectNodeProps) {
    25    const items: Array<api.Node> = props.items.map(fn => fn.data.node).sort(api.sortNodesByLabelAsc);
    26    const [selectedItem, setSelectedItem] = useState<api.Node | undefined>();
    27  
    28    const filterItem: ItemPredicate<api.Node> = (query, item, _index, exactMatch) => {
    29      const normalizedItem = api.nodeLabelOrID(item);
    30      const normalizedQuery = query.toLowerCase();
    31      return normalizedItem.indexOf(normalizedQuery) >= 0;
    32    };
    33  
    34    const renderItem: ItemRenderer<api.Node> = (item, { handleClick, handleFocus, modifiers, query }) => {
    35      if (!modifiers.matchesPredicate) {
    36        return null;
    37      }
    38      const nodeType = nodeTypes[item.metadata.node_type]
    39      return (
    40        <MenuItem
    41          active={modifiers.active}
    42          disabled={modifiers.disabled}
    43          key={`item_${item.id}`}
    44          icon={nodeType.icon}
    45          onClick={handleClick}
    46          onFocus={handleFocus}
    47          roleStructure="listoption"
    48          text={`[${item.metadata.node_type}] ${item.label}`}
    49        />
    50      );
    51    };
    52  
    53    const onItemSelect = (item: api.Node) => {
    54      props.onItemSelect(item);
    55      setSelectedItem(item);
    56    }
    57  
    58    useEffect(() => {
    59      if (props.value) {
    60        setSelectedItem(items.find(i => i.id === props.value))
    61      }
    62    }, [props.value])
    63  
    64    return (
    65      <Select<api.Node>
    66        items={items}
    67        itemPredicate={filterItem}
    68        itemRenderer={renderItem}
    69        noResults={<MenuItem disabled={true} text="No results." roleStructure="listoption" />}
    70        onItemSelect={onItemSelect}
    71        disabled={props.disabled}
    72        fill={props.fill}
    73      >
    74        <Button autoFocus={props.autoFocus} tabIndex={props.tabIndex} id={props.id} text={selectedItem ? api.nodeLabelOrID(selectedItem) : "Please select a node"} fill={props.fill} disabled={props.disabled} rightIcon="double-caret-vertical" placeholder="Please select a node" />
    75      </Select>
    76    )
    77  }