node-list.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import React, { FC } from 'react';
  6. import styled from 'styled-components';
  7. import { NodePanelRenderProps } from '@flowgram.ai/free-node-panel-plugin';
  8. import { useClientContext, WorkflowNodeEntity } from '@flowgram.ai/free-layout-editor';
  9. import { FlowNodeRegistry } from '../../typings';
  10. import { nodeRegistries } from '../../nodes';
  11. const NodeWrap = styled.div`
  12. width: 100%;
  13. height: 32px;
  14. border-radius: 5px;
  15. display: flex;
  16. align-items: center;
  17. cursor: pointer;
  18. font-size: 19px;
  19. padding: 0 15px;
  20. &:hover {
  21. background-color: hsl(252deg 62% 55% / 9%);
  22. color: hsl(252 62% 54.9%);
  23. }
  24. `;
  25. const NodeLabel = styled.div`
  26. font-size: 12px;
  27. margin-left: 10px;
  28. `;
  29. interface NodeProps {
  30. label: string;
  31. icon: JSX.Element;
  32. onClick: React.MouseEventHandler<HTMLDivElement>;
  33. disabled: boolean;
  34. }
  35. function Node(props: NodeProps) {
  36. return (
  37. <NodeWrap
  38. data-testid={`demo-free-node-list-${props.label}`}
  39. onClick={props.disabled ? undefined : props.onClick}
  40. style={props.disabled ? { opacity: 0.3 } : {}}
  41. >
  42. <div style={{ fontSize: 14 }}>{props.icon}</div>
  43. <NodeLabel>{props.label}</NodeLabel>
  44. </NodeWrap>
  45. );
  46. }
  47. const NodesWrap = styled.div`
  48. max-height: 500px;
  49. overflow: auto;
  50. &::-webkit-scrollbar {
  51. display: none;
  52. }
  53. `;
  54. interface NodeListProps {
  55. onSelect: NodePanelRenderProps['onSelect'];
  56. containerNode?: WorkflowNodeEntity;
  57. }
  58. export const NodeList: FC<NodeListProps> = (props) => {
  59. const { onSelect, containerNode } = props;
  60. const context = useClientContext();
  61. const handleClick = (e: React.MouseEvent, registry: FlowNodeRegistry) => {
  62. const json = registry.onAdd?.(context);
  63. onSelect({
  64. nodeType: registry.type as string,
  65. selectEvent: e,
  66. nodeJSON: json,
  67. });
  68. };
  69. return (
  70. <NodesWrap style={{ width: 80 * 2 + 20 }}>
  71. {nodeRegistries
  72. .filter((register) => register.meta.nodePanelVisible !== false)
  73. .filter((register) => {
  74. if (register.meta.onlyInContainer) {
  75. return register.meta.onlyInContainer === containerNode?.flowNodeType;
  76. }
  77. return true;
  78. })
  79. .map((registry) => (
  80. <Node
  81. key={registry.type}
  82. disabled={!(registry.canAdd?.(context) ?? true)}
  83. icon={
  84. <img style={{ width: 10, height: 10, borderRadius: 4 }} src={registry.info?.icon} />
  85. }
  86. label={registry.type as string}
  87. onClick={(e) => handleClick(e, registry)}
  88. />
  89. ))}
  90. </NodesWrap>
  91. );
  92. };