Skip to content

Commit

Permalink
feat: loop node (#35)
Browse files Browse the repository at this point in the history
* feat: loop node

* feat: loop node arrow

* fix: arrow rotate

* fix: add node in loop

* feat: loop node

* fix: loop node registerNodes

* feat: docs
  • Loading branch information
lixiao1022 authored Jan 14, 2023
1 parent 408840b commit 1766575
Show file tree
Hide file tree
Showing 21 changed files with 395 additions and 52 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ export default Demo;
| initialNodeData | the initial data when add new node | `Record<string, any>` | | - |
| isStart | Is start node | `boolean` | | false |
| isEnd | Is end node | `boolean` | | false |
| isLoop | Is loop node | `boolean` | | false | 1.4.6 |
| name | The name of node | `string` || - |
| removeConfirmTitle | The confirmation information before deleting the node. The [title](https://ant.design/components/popconfirm/#API) of Popconfirm | `string` \| `ReactNode` | | Are you sure to remove this node? |
| showPracticalBranchNode | - | `boolean` | | false | 1.1.0 |
Expand Down Expand Up @@ -336,11 +337,12 @@ In the context of FlowBuilder the following hooks can be used

#### useAction

| Property | Description | Type |
| :--------- | :-------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------ |
| clickNode | click node | `(node: INode = useContext(NodeContext)) => void` |
| addNode | add one node. (Get the current node through NodeContext when there is no node property) | `(node: INode, newNodeType: string) => void` \| `(newNodeType: string) => void` |
| removeNode | remove one node or more nodes. | `(targetNode: INode \| INode[] = useContext(NodeContext)) => void` |
| Property | Description | Type | Version |
| :------------ | :-------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------ | :------ |
| clickNode | click node | `(node: INode = useContext(NodeContext)) => void` |
| addNode | add one node. (Get the current node through NodeContext when there is no node property) | `(node: INode, newNodeType: string) => void` \| `(newNodeType: string) => void` |
| addNodeInLoop | add one node in loop node. | `(newNodeType: string) => void` | 1.4.6 |
| removeNode | remove one node or more nodes. | `(targetNode: INode \| INode[] = useContext(NodeContext)) => void` |

#### useDrawer

Expand Down
12 changes: 7 additions & 5 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ export default Demo;
| initialNodeData | 增加节点时初始化数据 | `Record<string, any>` | | - |
| isStart | 是否为开始节点 | `boolean` | | false |
| isEnd | 是否为结束节点 | `boolean` | | false |
| isLoop | 是否为循环节点 | `boolean` | | false | 1.4.6 |
| name | 节点名称 | `string` || - |
| removeConfirmTitle | 删除节点前的提示信息。Popconfirm 组件的 [title](https://ant.design/components/popconfirm/#API) 属性 | `string` \| `ReactNode` | | Are you sure to remove this node? |
| showPracticalBranchNode | - | `boolean` | | false | 1.1.0 |
Expand Down Expand Up @@ -336,11 +337,12 @@ export default Demo;

#### useAction

| 属性 | 说明 | 类型 |
| :--------- | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------------ |
| clickNode | 点击一个节点 | `(node: INode = useContext(NodeContext)) => void` |
| addNode | 增加一个节点,不指定操作节点时,内部通过 NodeContext 获取到当前所在节点实现 | `(node: INode, newNodeType: string) => void` \| `(newNodeType: string) => void` |
| removeNode | 删除一个/多个节点 | `(targetNode: INode \| INode[] = useContext(NodeContext)) => void` |
| 属性 | 说明 | 类型 | 版本 |
| :------------ | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------------ | :---- |
| clickNode | 点击一个节点 | `(node: INode = useContext(NodeContext)) => void` |
| addNode | 增加一个节点,不指定操作节点时,内部通过 NodeContext 获取到当前所在节点实现 | `(node: INode, newNodeType: string) => void` \| `(newNodeType: string) => void` |
| addNodeInLoop | 循环节点内增加一个节点 | `(newNodeType: string) => void` | 1.4.6 |
| removeNode | 删除一个/多个节点 | `(targetNode: INode \| INode[] = useContext(NodeContext)) => void` |

#### useDrawer

Expand Down
12 changes: 7 additions & 5 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ order: 8
| initialNodeData | 增加节点时初始化数据 | `Record<string, any>` | | - |
| isStart | 是否为开始节点 | `boolean` | | false |
| isEnd | 是否为结束节点 | `boolean` | | false |
| isLoop | 是否为循环节点 | `boolean` | | false | 1.4.6 |
| name | 节点名称 | `string` || - |
| removeConfirmTitle | 删除节点前的提示信息。Popconfirm 组件的 [title](https://ant.design/components/popconfirm/#API) 属性 | `string` \| `ReactNode` | | Are you sure to remove this node? |
| showPracticalBranchNode | - | `boolean` | | false | 1.1.0 |
Expand Down Expand Up @@ -192,11 +193,12 @@ order: 8

### useAction

| 属性 | 说明 | 类型 |
| :--------- | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------------ |
| clickNode | 点击一个节点 | `(node: INode = useContext(NodeContext)) => void` |
| addNode | 增加一个节点,不指定操作节点时,内部通过 NodeContext 获取到当前所在节点实现 | `(node: INode, newNodeType: string) => void` \| `(newNodeType: string) => void` |
| removeNode | 删除一个/多个节点 | `(targetNode: INode \| INode[] = useContext(NodeContext)) => void` |
| 属性 | 说明 | 类型 | 版本 |
| :------------ | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------------ | :---- |
| clickNode | 点击一个节点 | `(node: INode = useContext(NodeContext)) => void` |
| addNode | 增加一个节点,不指定操作节点时,内部通过 NodeContext 获取到当前所在节点实现 | `(node: INode, newNodeType: string) => void` \| `(newNodeType: string) => void` |
| addNodeInLoop | 循环节点内增加一个节点 | `(newNodeType: string) => void` | 1.4.6 |
| removeNode | 删除一个/多个节点 | `(targetNode: INode \| INode[] = useContext(NodeContext)) => void` |

### useDrawer

Expand Down
6 changes: 6 additions & 0 deletions docs/changeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ order: 9

- 增加 `showArrow`,显示箭头
- 增加 `arrowIcon`,在 `showArrow: true` 的前提下可以自定义箭头

## 1.4.6

`2023-01-14`

- 增加 `isLoop`,循环节点
6 changes: 6 additions & 0 deletions docs/demo/node/form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ const registerNodes: IRegisterNode[] = [
name: '分支节点',
conditionNodeType: 'condition',
},
{
type: 'loop',
name: '循环节点',
displayComponent: NodeDisplay,
isLoop: true,
},
];

const defaultNodes = [
Expand Down
5 changes: 5 additions & 0 deletions docs/demo/node/register/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ const registerNodes: IRegisterNode[] = [
name: '分支节点',
conditionNodeType: 'condition',
},
{
type: 'loop',
name: '循环节点',
isLoop: true,
},
];

const Index = () => {
Expand Down
4 changes: 3 additions & 1 deletion docs/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ order: 2

不同的使用场景对节点的数量、类型、样式等都有不用的诉求,react-flow-builder 提供了 `registerNodes` 属性,通过节点注册的机制满足个性化场景。

分为 5 种节点类型:
分为 6 种节点类型:

- 开始节点
- 结束节点
- 分支节点
- 条件节点
- 普通节点
- 循环节点

## 节点数量

Expand All @@ -25,6 +26,7 @@ order: 2
| type | 节点类型 | string || - |
| isStart | 是否为开始节点 | boolean | | false |
| isEnd | 是否为结束节点 | boolean | | false |
| isLoop | 是否为循环节点 | boolean | | false |

<br>

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-flow-builder",
"version": "1.4.5",
"version": "1.4.6",
"keywords": [
"flow",
"builder",
Expand Down Expand Up @@ -59,7 +59,7 @@
"prettier": "^2.2.1",
"react": ">=16",
"react-dom": ">=16",
"react-flow-builder": "^1.4.5",
"react-flow-builder": "^1.4.6",
"yorkie": "^2.0.0"
},
"dependencies": {
Expand Down
16 changes: 12 additions & 4 deletions src/AddButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ import AddNormalIcon from '../icons/add-normal.svg';
import AddBranchIcon from '../icons/add-branch.svg';
import './index.less';

const AddNodeButton: React.FC = () => {
interface IProps {
inLoop?: boolean;
}

const AddNodeButton: React.FC<IProps> = (props) => {
const { inLoop } = props;

const {
registerNodes,
nodes,
Expand All @@ -29,7 +35,9 @@ const AddNodeButton: React.FC = () => {

const node = useContext(NodeContext);

const { addNode } = useAction();
const { addNode, addNodeInLoop } = useAction();

const handleAdd = inLoop ? addNodeInLoop : addNode;

const [visible, setVisible] = useState(false);

Expand All @@ -55,12 +63,12 @@ const AddNodeButton: React.FC = () => {
);

const handleAddNode = (newNodeType: string) => {
addNode(newNodeType);
handleAdd(newNodeType);
setVisible(false);
};

const handleDrop = () => {
addNode(dragType);
handleAdd(dragType);
};

const addableOptions = AddableComponent ? (
Expand Down
24 changes: 24 additions & 0 deletions src/Arrow/index.less
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
.flow-builder-arrow {
display: inline-flex;
}

.flow-builder-loop-node__content {
> .flow-builder-arrow {
position: absolute;
}
}

.flow-builder-vertical {
.flow-builder-loop-node__content {
> .flow-builder-arrow {
transform: rotate(180deg);
top: 2px;
left: -9px;
}
}
}

.flow-builder-horizontal {
.flow-builder-arrow {
transform: rotate(-90deg);
}
.flow-builder-loop-node__content {
> .flow-builder-arrow {
transform: rotate(90deg);
bottom: -9px;
left: 2px;
}
}
}
10 changes: 8 additions & 2 deletions src/Arrow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import { BuilderContext } from '../contexts';
import './index.less';

const Arrow = () => {
const { lineColor, showArrow, arrowIcon } = useContext(BuilderContext);
const { lineColor, backgroundColor, showArrow, arrowIcon } =
useContext(BuilderContext);

return showArrow ? (
<div className="flow-builder-arrow">
<div
className="flow-builder-arrow"
style={{
backgroundColor,
}}
>
{arrowIcon || (
<svg
viewBox="0 0 1024 1024"
Expand Down
3 changes: 3 additions & 0 deletions src/Builder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
CommonNode,
BranchNode,
ConditionNode,
LoopNode,
} from '../Nodes';
import { HistoryTool, ZoomTool } from '../Tools';
import DragPanel from '../DragPanel';
Expand Down Expand Up @@ -86,6 +87,8 @@ const Builder = forwardRef<IFlowBuilderMethod>((props, ref) => {
renderNext={render}
/>
);
case 'loop':
return <LoopNode renderNext={render} />;
default:
return <CommonNode />;
}
Expand Down
26 changes: 20 additions & 6 deletions src/Lines/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,55 @@

.flow-builder-vertical {
.flow-builder-line__cover {
&.cover-condition-start {
&.cover-condition-start,
&.cover-loop-start {
top: 0;
}
&.cover-condition-end {
&.cover-condition-end,
&.cover-loop-end {
bottom: 0;
}
&.cover-condition-start,
&.cover-condition-end {
&.cover-first {
&.cover-first,
&.cover-middle {
right: 0;
}
&.cover-last {
left: 0;
}
}
&.cover-loop-start,
&.cover-loop-end {
left: 0;
}
}
}

.flow-builder-horizontal {
.flow-builder-line__cover {
&.cover-condition-start {
&.cover-condition-start,
&.cover-loop-start {
left: 0;
}
&.cover-condition-end {
&.cover-condition-end,
&.cover-loop-end {
right: 0;
}
&.cover-condition-start,
&.cover-condition-end {
&.cover-first {
&.cover-first,
&.cover-middle {
bottom: 0;
}
&.cover-last {
top: 0;
}
}
&.cover-loop-start,
&.cover-loop-end {
bottom: 0;
}
}
}

Expand Down
26 changes: 12 additions & 14 deletions src/Nodes/ConditionNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ const ConditionNode: React.FC<IProps> = (props) => {
}
};

const coverIndexClassName = ((index: number, total: number) => {
if (index === 0) {
return 'cover-first';
}
if (index === total - 1) {
return 'cover-last';
}
return 'cover-middle';
})(conditionIndex, conditionCount);

return (
<div
className={`flow-builder-node flow-builder-condition-node ${
Expand All @@ -78,23 +88,11 @@ const ConditionNode: React.FC<IProps> = (props) => {
<>
<CoverLine
full={conditionIndex !== 0 && conditionIndex !== conditionCount - 1}
className={`cover-condition-start ${
conditionIndex === 0
? 'cover-first'
: conditionIndex === conditionCount - 1
? 'cover-last'
: ''
}`}
className={`cover-condition-start ${coverIndexClassName}`}
/>
<CoverLine
full={conditionIndex !== 0 && conditionIndex !== conditionCount - 1}
className={`cover-condition-end ${
conditionIndex === 0
? 'cover-first'
: conditionIndex === conditionCount - 1
? 'cover-last'
: ''
}`}
className={`cover-condition-end ${coverIndexClassName}`}
/>
</>
) : null}
Expand Down
Loading

0 comments on commit 1766575

Please sign in to comment.