Skip to content

Commit 351ae11

Browse files
Merge pull request #8 from CarstenWickner/finalise-editing-feature
2 parents 441adc7 + 4de5ff2 commit 351ae11

50 files changed

Lines changed: 3282 additions & 1989 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ In active development, i.e. not yet published
2727
| --- | --- |
2828
| `flow` (required) | model description of the flow-chart to be displayed |
2929
| `options` | display options: <ul><li>`verticalAlign`: either `"top"`, `"middle"` (default) or `"bottom"`</li></ul> |
30-
| `renderContent` (required) | custom render function for the "content" elements |
30+
| `renderStep` (required) | custom render function for the "step" elements |
3131
| `renderGatewayConditionType` | custom render function for the condition label following an exclusive gateway |
3232
| `renderGatewayConditionValue` | custom render function for the condition label in front of an element following an exclusive gateway |
3333

package.json

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,60 +32,59 @@
3232
"defaults"
3333
],
3434
"devDependencies": {
35-
"@mdx-js/loader": "~1.5.1",
36-
"@storybook/addon-actions": "~5.3.9",
37-
"@storybook/addon-docs": "~5.3.9",
38-
"@storybook/react": "~5.3.9",
39-
"@types/enzyme": "~3.10.3",
40-
"@types/enzyme-adapter-react-16": "~1.0.5",
35+
"@mdx-js/loader": "^1.5.5",
36+
"@storybook/addon-actions": "^5.3.13",
37+
"@storybook/addon-docs": "^5.3.13",
38+
"@storybook/react": "^5.3.13",
39+
"@types/enzyme": "^3.10.5",
40+
"@types/enzyme-adapter-react-16": "^1.0.6",
4141
"@types/jest": "24.0.23",
42-
"@types/lodash.clonedeep": "^4.5.6",
4342
"@types/prop-types": "^15.7.2",
44-
"@types/react": "^16.9.5",
45-
"@types/react-dom": "^16.9.2",
46-
"@types/uuid": "^3.4.6",
47-
"@typescript-eslint/eslint-plugin": "^2.11.0",
48-
"@typescript-eslint/parser": "^2.9.0",
49-
"@typescript-eslint/typescript-estree": "^2.9.0",
43+
"@types/react": "^16.9.20",
44+
"@types/react-dom": "^16.9.5",
45+
"@types/uuid": "^3.4.7",
46+
"@typescript-eslint/eslint-plugin": "^2.20.0",
47+
"@typescript-eslint/parser": "^2.20.0",
48+
"@typescript-eslint/typescript-estree": "^2.20.0",
5049
"@wessberg/rollup-plugin-ts": "~1.1.73",
5150
"awesome-typescript-loader": "~5.2.1",
5251
"babel-loader": "~8.0.6",
53-
"babel-preset-react-app": "~9.1.0",
54-
"coveralls": "~3.0.6",
52+
"babel-preset-react-app": "~9.1.1",
53+
"coveralls": "^3.0.6",
5554
"css-loader": "~3.3.0",
56-
"enzyme": "~3.10.0",
57-
"enzyme-adapter-react-16": "~1.15.0",
58-
"enzyme-to-json": "~3.4.2",
59-
"eslint": "^6.7.0",
60-
"eslint-config-prettier": "^6.7.0",
61-
"eslint-plugin-import": "^2.18.2",
62-
"eslint-plugin-jsdoc": "^18.4.0",
55+
"enzyme": "^3.10.0",
56+
"enzyme-adapter-react-16": "^1.15.2",
57+
"enzyme-to-json": "^3.4.4",
58+
"eslint": "^6.8.0",
59+
"eslint-config-prettier": "^6.10.0",
60+
"eslint-plugin-import": "^2.20.1",
61+
"eslint-plugin-jsdoc": "^18.11.0",
6362
"eslint-plugin-jsx-a11y": "^6.2.3",
64-
"eslint-plugin-prettier": "^3.1.1",
65-
"eslint-plugin-react": "^7.17.0",
66-
"gh-pages": "~2.1.1",
63+
"eslint-plugin-prettier": "^3.1.2",
64+
"eslint-plugin-react": "^7.18.3",
65+
"gh-pages": "^2.1.1",
6766
"identity-obj-proxy": "~3.0.0",
6867
"jest": "24.0.0",
69-
"mathsass": "^0.11.0",
68+
"mathsass": "~0.11.0",
7069
"node-sass": "~4.13.1",
7170
"prettier": "~1.19.1",
72-
"react-docgen-typescript-loader": "~3.6.0",
73-
"rollup": "~1.27.0",
74-
"rollup-plugin-postcss": "~2.0.3",
71+
"react-dnd-test-backend": "^10.0.2",
72+
"react-docgen-typescript-loader": "^3.6.0",
73+
"rollup": "~1.27.14",
74+
"rollup-plugin-postcss": "~2.0.6",
7575
"rollup-plugin-terser": "~5.1.2",
76-
"sass-loader": "~8.0.0",
77-
"style-loader": "~1.0.0",
78-
"stylelint": "^12.0.0",
79-
"stylelint-config-recommended-scss": "^4.1.0",
80-
"stylelint-scss": "~3.13.0",
81-
"ts-jest": "~24.2.0",
82-
"typescript": "~3.6.4"
76+
"sass-loader": "~8.0.2",
77+
"style-loader": "~1.0.2",
78+
"stylelint": "^12.0.1",
79+
"stylelint-config-recommended-scss": "^4.2.0",
80+
"stylelint-scss": "^3.13.0",
81+
"ts-jest": "^24.2.0",
82+
"typescript": "^3.6.5"
8383
},
8484
"dependencies": {
85-
"lodash.clonedeep": "^4.5.0",
86-
"react-dnd": "^10.0.2",
87-
"react-dnd-html5-backend": "^10.0.2",
88-
"uuid": "^3.4.0"
85+
"react-dnd": "~10.0.2",
86+
"react-dnd-html5-backend": "~10.0.2",
87+
"uuid": "~3.4.0"
8988
},
9089
"peerDependencies": {
9190
"prop-types": "^15.7.2",

src/component/EditMenu.tsx

Lines changed: 85 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,146 @@
11
import * as React from "react";
22

33
import { EditMenuItem } from "./EditMenuItem";
4-
import { FlowElementReference } from "../model/FlowElement";
5-
import { addContentElement, addDivergingGateway } from "../model/action/addElement";
4+
import { addStepElement, addDivergingGateway } from "../model/action/addElement";
65
import { addBranch } from "../model/action/addBranch";
76
import { isChangeNextElementAllowed } from "../model/action/changeNextElement";
87
import { removeElement, isRemoveElementAllowed } from "../model/action/removeElement";
98

10-
import { SelectableElementType, EditActionResult, DraggableType } from "../types/EditAction";
11-
import { FlowModelerProps, MenuOptions } from "../types/FlowModelerProps";
12-
import { ElementType } from "../types/GridCellData";
9+
import { StepNode, ConvergingGatewayNode, DivergingGatewayNode, DivergingGatewayBranch, ElementType, StartNode } from "../types/ModelElement";
10+
import { EditActionResult, DraggableType, DraggedLinkContext } from "../types/EditAction";
11+
import { FlowModelerProps } from "../types/FlowModelerProps";
1312

1413
const onClickStopPropagation = (event: React.MouseEvent): void => event.stopPropagation();
1514

1615
export class EditMenu extends React.Component<{
17-
targetType: SelectableElementType;
18-
referenceElement?: FlowElementReference;
19-
branchIndex?: number;
20-
menuOptions?: FlowModelerProps["options"]["editActions"];
16+
referenceElement: StartNode | StepNode | ConvergingGatewayNode | DivergingGatewayNode | DivergingGatewayBranch;
17+
editActions?: FlowModelerProps["editActions"];
2118
onChange: (change: (originalFlow: FlowModelerProps["flow"]) => EditActionResult) => void;
2219
}> {
23-
isNextElementReferencedByOthers = (): boolean => {
24-
const { referenceElement, branchIndex } = this.props;
25-
if (!referenceElement) {
26-
return false;
27-
}
28-
return referenceElement.getFollowingElements()[branchIndex || 0].getPrecedingElements().length > 1;
29-
};
30-
3120
renderMenuItem(
32-
options: MenuOptions,
21+
options:
22+
| {
23+
className?: string;
24+
title?: string;
25+
}
26+
| undefined,
3327
defaultClassName: string,
3428
onClick?: (event: React.MouseEvent) => void,
3529
dragType?: DraggableType
3630
): React.ReactNode {
37-
const { targetType, referenceElement, branchIndex } = this.props;
38-
if (options && options.isActionAllowed && !options.isActionAllowed(targetType, referenceElement, branchIndex)) {
39-
return null;
40-
}
41-
const dragItem =
31+
const { referenceElement } = this.props;
32+
const dragItem: DraggedLinkContext =
4233
dragType === DraggableType.LINK &&
43-
(targetType === ElementType.Content || targetType == ElementType.ConnectGatewayToElement) &&
44-
this.isNextElementReferencedByOthers()
45-
? { type: dragType, originType: targetType, originElement: referenceElement, originBranchIndex: branchIndex }
34+
(referenceElement.type === ElementType.StepNode || referenceElement.type === ElementType.DivergingGatewayBranch) &&
35+
isChangeNextElementAllowed(referenceElement)
36+
? { type: dragType, originElement: referenceElement }
4637
: undefined;
4738
return <EditMenuItem key={defaultClassName} options={options} defaultClassName={defaultClassName} onClick={onClick} dragItem={dragItem} />;
4839
}
4940

50-
onAddContentElementClick = (): void => {
51-
const { targetType, onChange, referenceElement, branchIndex } = this.props;
52-
if (targetType !== ElementType.GatewayDiverging) {
53-
onChange((originalFlow) => addContentElement(originalFlow, targetType, {}, referenceElement, branchIndex));
54-
}
41+
onAddStepElementClick = (): void => {
42+
const { onChange, referenceElement, editActions } = this.props;
43+
const leadingElement = (referenceElement as unknown) as StartNode | StepNode | ConvergingGatewayNode | DivergingGatewayBranch;
44+
const options = editActions && editActions.addFollowingStepElement;
45+
const stepData = (options && options.getStepData && options.getStepData(leadingElement)) || {};
46+
onChange((originalFlow) => addStepElement(originalFlow, leadingElement, stepData));
5547
};
5648

57-
renderAddContentElementItem(): React.ReactNode {
58-
const { targetType, menuOptions } = this.props;
59-
if (targetType === ElementType.GatewayDiverging) {
49+
renderAddStepElementItem(): React.ReactNode {
50+
const { referenceElement, editActions } = this.props;
51+
if (referenceElement.type === ElementType.DivergingGatewayNode) {
6052
return null;
6153
}
62-
return this.renderMenuItem(menuOptions ? menuOptions.addFollowingContentElement : undefined, "add-content", this.onAddContentElementClick);
54+
const options = editActions ? editActions.addFollowingStepElement : undefined;
55+
if (options && options.isActionAllowed && !options.isActionAllowed(referenceElement)) {
56+
return null;
57+
}
58+
return this.renderMenuItem(options, "add-step", this.onAddStepElementClick);
6359
}
6460

6561
onAddDivergingGatewayClick = (): void => {
66-
const { targetType, onChange, referenceElement, branchIndex } = this.props;
67-
if (targetType !== ElementType.GatewayDiverging) {
68-
onChange((originalFlow) => addDivergingGateway(originalFlow, targetType, {}, referenceElement, branchIndex));
69-
}
62+
const { referenceElement, onChange, editActions } = this.props;
63+
const leadingElement = (referenceElement as unknown) as StartNode | StepNode | ConvergingGatewayNode | DivergingGatewayBranch;
64+
const options = editActions && editActions.addFollowingDivergingGateway;
65+
const gatewayData = (options && options.getGatewayData && options.getGatewayData(leadingElement)) || undefined;
66+
const branchConditionData = (options && options.getBranchConditionData && options.getBranchConditionData(leadingElement)) || undefined;
67+
onChange((originalFlow) => addDivergingGateway(originalFlow, leadingElement, gatewayData, branchConditionData));
7068
};
7169

7270
renderAddDivergingGatewayItem(): React.ReactNode {
73-
const { targetType, menuOptions } = this.props;
74-
if (targetType === ElementType.GatewayDiverging) {
71+
const { referenceElement, editActions } = this.props;
72+
if (referenceElement && referenceElement.type === ElementType.DivergingGatewayNode) {
7573
return null;
7674
}
77-
return this.renderMenuItem(
78-
menuOptions ? menuOptions.addFollowingDivergingGateway : undefined,
79-
"add-gateway",
80-
this.onAddDivergingGatewayClick
81-
);
75+
const options = editActions ? editActions.addFollowingDivergingGateway : undefined;
76+
if (
77+
options &&
78+
options.isActionAllowed &&
79+
!options.isActionAllowed((referenceElement as unknown) as undefined | StepNode | DivergingGatewayBranch | ConvergingGatewayNode)
80+
) {
81+
return null;
82+
}
83+
return this.renderMenuItem(options, "add-gateway", this.onAddDivergingGatewayClick);
8284
}
8385

8486
onAddDivergingBranchClick = (): void => {
85-
const { targetType, onChange, referenceElement } = this.props;
86-
if (targetType === ElementType.GatewayDiverging) {
87-
onChange((originalFlow) => addBranch(originalFlow, {}, referenceElement));
88-
}
87+
const { referenceElement, onChange, editActions } = this.props;
88+
const gateway = (referenceElement as unknown) as DivergingGatewayNode;
89+
const options = editActions && editActions.addDivergingBranch;
90+
const branchConditionData = (options && options.getBranchConditionData && options.getBranchConditionData(gateway)) || undefined;
91+
onChange((originalFlow) => addBranch(originalFlow, gateway, branchConditionData));
8992
};
9093

9194
renderAddDivergingBranchItem(): React.ReactNode {
92-
const { targetType, menuOptions } = this.props;
93-
if (targetType !== ElementType.GatewayDiverging) {
95+
const { referenceElement, editActions } = this.props;
96+
if (referenceElement.type !== ElementType.DivergingGatewayNode) {
9497
return null;
9598
}
96-
return this.renderMenuItem(menuOptions ? menuOptions.addDivergingBranch : undefined, "add-branch", this.onAddDivergingBranchClick);
99+
const options = editActions ? editActions.addDivergingBranch : undefined;
100+
if (options && options.isActionAllowed && !options.isActionAllowed(referenceElement)) {
101+
return null;
102+
}
103+
return this.renderMenuItem(options, "add-branch", this.onAddDivergingBranchClick);
97104
}
98105

99106
renderChangeNextElementItem(): React.ReactNode {
100-
const { targetType, referenceElement, branchIndex, menuOptions } = this.props;
101-
if (isChangeNextElementAllowed(targetType, referenceElement, branchIndex)) {
102-
return this.renderMenuItem(menuOptions ? menuOptions.changeNextElement : undefined, "change-next", undefined, DraggableType.LINK);
107+
const { referenceElement, editActions } = this.props;
108+
if (
109+
(referenceElement.type !== ElementType.StepNode && referenceElement.type !== ElementType.DivergingGatewayBranch) ||
110+
!isChangeNextElementAllowed(referenceElement)
111+
) {
112+
return null;
103113
}
104-
return null;
114+
const options = editActions ? editActions.changeNextElement : undefined;
115+
if (options && options.isActionAllowed && !options.isActionAllowed(referenceElement)) {
116+
return null;
117+
}
118+
return this.renderMenuItem(options, "change-next", undefined, DraggableType.LINK);
105119
}
106120

107121
onRemoveClick = (): void => {
108-
const { targetType, onChange, referenceElement, branchIndex } = this.props;
109-
if (targetType === ElementType.Content || targetType === ElementType.ConnectGatewayToElement) {
110-
onChange((originalFlow) => removeElement(originalFlow, targetType, referenceElement, branchIndex));
111-
}
122+
const { referenceElement, onChange } = this.props;
123+
onChange((originalFlow) => removeElement(originalFlow, (referenceElement as unknown) as StepNode | DivergingGatewayBranch));
112124
};
113125

114126
renderRemoveItem(): React.ReactNode {
115-
const { targetType, referenceElement, branchIndex, menuOptions } = this.props;
116-
if (isRemoveElementAllowed(targetType, referenceElement, branchIndex)) {
117-
return this.renderMenuItem(menuOptions ? menuOptions.removeElement : undefined, "remove", this.onRemoveClick);
127+
const { referenceElement, editActions } = this.props;
128+
if (
129+
(referenceElement.type !== ElementType.StepNode && referenceElement.type !== ElementType.DivergingGatewayBranch) ||
130+
!isRemoveElementAllowed(referenceElement)
131+
) {
132+
return null;
118133
}
119-
return null;
134+
const options = editActions ? editActions.removeElement : undefined;
135+
if (options && options.isActionAllowed && !options.isActionAllowed(referenceElement)) {
136+
return null;
137+
}
138+
return this.renderMenuItem(editActions ? editActions.removeElement : undefined, "remove", this.onRemoveClick);
120139
}
121140

122141
render(): React.ReactNode {
123142
const menuItems = [
124-
this.renderAddContentElementItem(),
143+
this.renderAddStepElementItem(),
125144
this.renderAddDivergingGatewayItem(),
126145
this.renderAddDivergingBranchItem(),
127146
this.renderChangeNextElementItem(),

src/component/FlowElementWrapper.tsx

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,47 @@
11
import * as React from "react";
22
import { useDrop } from "react-dnd";
33

4-
import { FlowElementReference } from "../model/FlowElement";
5-
import { DraggableType, DraggedLinkContext, onLinkDropCallback } from "../types/EditAction";
64
import { isFlowValid } from "../model/pathValidationUtils";
75

8-
const isTargetAncestorOfDragItem = (originElement?: FlowElementReference, referenceElement?: FlowElementReference): boolean => {
9-
if (!originElement || !referenceElement) {
10-
// reference cannot be ancestor of the start; and end reference cannot be ancestor of anything
11-
return false;
12-
}
6+
import { StepNode, ConvergingGatewayNode, DivergingGatewayNode, ElementType, EndNode, ModelElementExclStart } from "../types/ModelElement";
7+
import { DraggableType, DraggedLinkContext, onLinkDropCallback } from "../types/EditAction";
8+
9+
const isTargetAncestorOfDragItem = (
10+
originElement: ModelElementExclStart,
11+
referenceElement: StepNode | DivergingGatewayNode | ConvergingGatewayNode | EndNode
12+
): boolean => {
1313
if (originElement === referenceElement) {
1414
return true;
1515
}
16-
return originElement.getPrecedingElements().some((preceding) => isTargetAncestorOfDragItem(preceding, referenceElement));
17-
};
18-
19-
const isDropValid = (referenceElement: FlowElementReference, onDrop: onLinkDropCallback) => (dragContext: DraggedLinkContext): boolean => {
20-
if (isTargetAncestorOfDragItem(dragContext.originElement, referenceElement)) {
21-
return false;
22-
}
23-
const currentNextElement = dragContext.originElement.getFollowingElements()[0];
24-
if (currentNextElement === referenceElement || (!referenceElement && currentNextElement.getFollowingElements().length === 0)) {
25-
// linking to the current follower will not change anything
26-
return false;
16+
if (originElement.type === ElementType.ConvergingGatewayNode) {
17+
return originElement.precedingBranches.some((branch) => isTargetAncestorOfDragItem(branch, referenceElement));
2718
}
28-
return isFlowValid(onDrop(referenceElement, dragContext, true).changedFlow);
19+
return (
20+
originElement.precedingElement.type !== ElementType.StartNode && isTargetAncestorOfDragItem(originElement.precedingElement, referenceElement)
21+
);
2922
};
3023

24+
const isDropValid = (referenceElement: StepNode | DivergingGatewayNode | ConvergingGatewayNode | EndNode, onDrop: onLinkDropCallback) => (
25+
dragContext: DraggedLinkContext
26+
): boolean =>
27+
!isTargetAncestorOfDragItem(dragContext.originElement, referenceElement) &&
28+
dragContext.originElement.followingElement !== referenceElement &&
29+
isFlowValid(onDrop(referenceElement, dragContext, true).changedFlow);
30+
3131
const disabledDropping: [{ isOver: boolean; canDrop: false }, React.LegacyRef<HTMLDivElement>] = [{ isOver: false, canDrop: false }, undefined];
3232

3333
export const FlowElementWrapper: React.FC<{
3434
elementTypeClassName: string;
35-
referenceElement?: FlowElementReference;
35+
referenceElement: StepNode | DivergingGatewayNode | ConvergingGatewayNode | EndNode;
3636
editMenu?: (() => React.ReactNode) | undefined;
3737
onLinkDrop?: onLinkDropCallback | undefined;
3838
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
39-
}> = ({ elementTypeClassName, referenceElement, editMenu, onLinkDrop: onDrop, onClick, children }) => {
40-
const [{ isOver, canDrop }, drop] = onDrop
39+
}> = ({ elementTypeClassName, referenceElement, editMenu, onLinkDrop, onClick, children }) => {
40+
const [{ isOver, canDrop }, drop] = onLinkDrop
4141
? useDrop({
4242
accept: DraggableType.LINK,
43-
canDrop: isDropValid(referenceElement, onDrop),
44-
drop: (dragContext: DraggedLinkContext) => onDrop(referenceElement, dragContext),
43+
canDrop: isDropValid(referenceElement, onLinkDrop),
44+
drop: (dragContext: DraggedLinkContext) => onLinkDrop(referenceElement, dragContext),
4545
collect: (monitor) => ({
4646
isOver: !!monitor.isOver(),
4747
canDrop: !!monitor.canDrop()

0 commit comments

Comments
 (0)