Skip to content

Commit

Permalink
Add initial bpmn diagram implementation (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiaslehnertum authored Oct 15, 2023
1 parent c6993d1 commit 491b6d2
Show file tree
Hide file tree
Showing 69 changed files with 2,453 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ dist/
/coverage/
/docs/build/
cypress
test-report.html
test-report.html
**/.DS_Store
3 changes: 2 additions & 1 deletion public/index.html.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<option value="ReachabilityGraph">Reachability Graph</option>
<option value="SyntaxTree">Syntax Tree</option>
<option value="Flowchart">Flowchart</option>
<option value="BPMN">BPMN (beta)</option>
</select>
</div>
</section>
Expand Down Expand Up @@ -88,4 +89,4 @@
</div>
</body>

</html>
</html>
5 changes: 5 additions & 0 deletions src/main/components/create-pane/create-pane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { composeSyntaxTreePreview } from '../../packages/syntax-tree/syntax-tree
import { composeFlowchartPreview } from '../../packages/flowchart/flowchart-diagram-preview';
import { ColorLegend } from '../../packages/common/color-legend/color-legend';
import { Separator } from './create-pane-styles';
import { composeBPMNPreview } from '../../packages/bpmn/bpmn-diagram-preview';
type OwnProps = {};

type StateProps = {
Expand Down Expand Up @@ -78,6 +79,10 @@ const getInitialState = ({ type, canvas, translate, colorEnabled }: Props) => {
break;
case UMLDiagramType.Flowchart:
previews.push(...composeFlowchartPreview(canvas, translate));
break;
case UMLDiagramType.BPMN:
previews.push(...composeBPMNPreview(canvas, translate));
break;
}
if (colorEnabled) {
utils.push(
Expand Down
23 changes: 23 additions & 0 deletions src/main/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,29 @@
},
"ColorLegend": {
"ColorLegend": "Farbe Beschreibung"
},
"BPMN": {
"BPMNTask": "Task",
"BPMNSubprocess": "Sub-Prozess",
"BPMNTransaction": "Transaktion",
"BPMNCallActivity": "Aufruf-Aktivität",
"BPMNAnnotation": "Annotation",
"BPMNStartEvent": "Start-Event",
"BPMNIntermediateEvent": "Event",
"BPMNEndEvent": "End-Event",
"BPMNGateway": "Gateway",
"BPMNComplexGateway": "Komplex",
"BPMNEventBasedGateway": "Ereignis-basiert",
"BPMNExclusiveGateway": "Exklusiv",
"BPMNExclusiveEventBasedGateway": "Exklusiv Ereignis-basiert",
"BPMNInclusiveGateway": "Inklusiv",
"BPMNParallelGateway": "Parallel",
"BPMNParallelEventBasedGateway": "Parallel Ereignis-basiert",
"BPMNConversation": "Konversation",
"BPMNCallConversation": "Aufruf-Konversation",
"BPMNSequenceFlow": "Sequenz",
"BPMNMessageFlow": "Nachricht",
"BPMNAssociationFlow": "Assoziation"
}
}
}
23 changes: 23 additions & 0 deletions src/main/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,29 @@
},
"ColorLegend": {
"ColorLegend": "Color Description"
},
"BPMN": {
"BPMNTask": "Task",
"BPMNSubprocess": "Subprocess",
"BPMNTransaction": "Transaction",
"BPMNCallActivity": "Call Activity",
"BPMNAnnotation": "Annotation",
"BPMNStartEvent": "Start",
"BPMNIntermediateEvent": "Event",
"BPMNEndEvent": "End",
"BPMNGateway": "Gateway",
"BPMNComplexGateway": "Complex",
"BPMNEventBasedGateway": "Event-based",
"BPMNInclusiveGateway": "Inclusive",
"BPMNExclusiveGateway": "Exclusive",
"BPMNExclusiveEventBasedGateway": "Exclusive Event-based",
"BPMNParallelGateway": "Parallel",
"BPMNParallelEventBasedGateway": "Parallel Event-based",
"BPMNConversation": "Conversation",
"BPMNCallConversation": "Call Conversation",
"BPMNSequenceFlow": "Sequence",
"BPMNMessageFlow": "Message",
"BPMNAssociationFlow": "Association"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { FunctionComponent } from 'react';
import { BPMNAnnotation } from './bpmn-annotation';
import { ThemedPath, ThemedRect } from '../../../components/theme/themedComponents';
import { Multiline } from '../../../utils/svg/multiline';

export const BPMNAnnotationComponent: FunctionComponent<Props> = ({ element }) => (
<g>
<ThemedRect
rx={10}
ry={10}
width={element.bounds.width}
height={element.bounds.height}
strokeColor="transparent"
fillColor="transparent"
/>
<ThemedPath
d={`M20,0 L10,0 A 10 10 280 0 0 0 10 L0,${element.bounds.height - 10} A 10 10 180 0 0 10 ${
element.bounds.height
} L20, ${element.bounds.height}`}
strokeColor={element.strokeColor}
fillColor="transparent"
/>
<Multiline
x={element.bounds.width / 2}
y={element.bounds.height / 2}
width={element.bounds.width}
height={element.bounds.height}
fontWeight="bold"
fill={element.textColor}
lineHeight={16}
capHeight={11}
>
{element.name}
</Multiline>
</g>
);

interface Props {
element: BPMNAnnotation;
fillColor?: string;
}
18 changes: 18 additions & 0 deletions src/main/packages/bpmn/bpmn-annotation/bpmn-annotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { BPMNElementType } from '..';
import { ILayer } from '../../../services/layouter/layer';
import { ILayoutable } from '../../../services/layouter/layoutable';
import { UMLElement } from '../../../services/uml-element/uml-element';
import { calculateNameBounds } from '../../../utils/name-bounds';
import { UMLElementType } from '../../uml-element-type';
import { UMLElementFeatures } from '../../../services/uml-element/uml-element-features';

export class BPMNAnnotation extends UMLElement {
static features: UMLElementFeatures = { ...UMLElement.features };

type: UMLElementType = BPMNElementType.BPMNAnnotation;

render(canvas: ILayer): ILayoutable[] {
this.bounds = calculateNameBounds(this, canvas);
return [this];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { FunctionComponent } from 'react';
import { BPMNCallActivity } from './bpmn-call-activity';
import { ThemedRect } from '../../../components/theme/themedComponents';
import { Multiline } from '../../../utils/svg/multiline';

export const BPMNCallActivityComponent: FunctionComponent<Props> = ({ element, fillColor }) => (
<g>
<ThemedRect
rx={10}
ry={10}
width={element.bounds.width}
height={element.bounds.height}
strokeColor={element.strokeColor}
strokeWidth={3}
fillColor={fillColor || element.fillColor}
/>
<Multiline
x={element.bounds.width / 2}
y={element.bounds.height / 2}
width={element.bounds.width}
height={element.bounds.height}
fontWeight="bold"
fill={element.textColor}
lineHeight={16}
capHeight={11}
>
{element.name}
</Multiline>
</g>
);

interface Props {
element: BPMNCallActivity;
fillColor?: string;
}
15 changes: 15 additions & 0 deletions src/main/packages/bpmn/bpmn-call-activity/bpmn-call-activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BPMNElementType } from '..';
import { ILayer } from '../../../services/layouter/layer';
import { ILayoutable } from '../../../services/layouter/layoutable';
import { UMLElement } from '../../../services/uml-element/uml-element';
import { calculateNameBounds } from '../../../utils/name-bounds';
import { UMLElementType } from '../../uml-element-type';

export class BPMNCallActivity extends UMLElement {
type: UMLElementType = BPMNElementType.BPMNCallActivity;

render(canvas: ILayer): ILayoutable[] {
this.bounds = calculateNameBounds(this, canvas);
return [this];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { FunctionComponent } from 'react';
import { BPMNConversation } from './bpmn-conversation';
import { ThemedPolyline } from '../../../components/theme/themedComponents';
import { Multiline } from '../../../utils/svg/multiline';

export const BPMNConversationComponent: FunctionComponent<Props> = ({ element }) => (
<g>
<ThemedPolyline
points={`${element.bounds.width / 4} 0, ${(element.bounds.width / 4) * 3} 0, ${element.bounds.width} ${
element.bounds.height / 2
}, ${(element.bounds.width / 4) * 3} ${element.bounds.height}, ${element.bounds.width / 4} ${
element.bounds.height
}, 0 ${element.bounds.height / 2}, ${element.bounds.width / 4} 0`}
strokeColor={element.strokeColor}
fillColor={element.fillColor}
strokeWidth={element.conversationType === 'call' ? 3 : 1}
/>
<Multiline
x={element.bounds.width + 10}
y={element.bounds.height / 2}
width={element.bounds.width}
height={element.bounds.height}
fill={element.textColor}
lineHeight={16}
capHeight={11}
textAnchor="start"
>
{element.name}
</Multiline>
</g>
);

export interface Props {
element: BPMNConversation;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { Component, ComponentClass } from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Button } from '../../../components/controls/button/button';
import { Divider } from '../../../components/controls/divider/divider';
import { TrashIcon } from '../../../components/controls/icon/trash';
import { Textfield } from '../../../components/controls/textfield/textfield';
import { I18nContext } from '../../../components/i18n/i18n-context';
import { localized } from '../../../components/i18n/localized';
import { ModelState } from '../../../components/store/model-state';
import { styled } from '../../../components/theme/styles';
import { UMLElementRepository } from '../../../services/uml-element/uml-element-repository';
import { Switch } from '../../../components/controls/switch/switch';
import { BPMNConversation, BPMNConversationType } from './bpmn-conversation';
import { Dropdown } from '../../../components/controls/dropdown/dropdown';

interface OwnProps {
element: BPMNConversation;
}

type StateProps = {};

interface DispatchProps {
update: typeof UMLElementRepository.update;
delete: typeof UMLElementRepository.delete;
}

type Props = OwnProps & StateProps & DispatchProps & I18nContext;

const enhance = compose<ComponentClass<OwnProps>>(
localized,
connect<StateProps, DispatchProps, OwnProps, ModelState>(null, {
update: UMLElementRepository.update,
delete: UMLElementRepository.delete,
}),
);

const Flex = styled.div`
display: flex;
align-items: baseline;
justify-content: space-between;
`;

class BPMNConversationUpdateComponent extends Component<Props> {
render() {
const { element } = this.props;

return (
<div>
<section>
<Flex>
<Textfield value={element.name} onChange={this.rename(element.id)} autoFocus />
<Button color="link" tabIndex={-1} onClick={this.delete(element.id)}>
<TrashIcon />
</Button>
</Flex>
<Divider />
</section>
<section>
<Dropdown value={element.conversationType} onChange={this.changeConversationType(element.id)} color="primary">
<Dropdown.Item value={'default'}>{this.props.translate('packages.BPMN.BPMNConversation')}</Dropdown.Item>
<Switch.Item value={'call'}>{this.props.translate('packages.BPMN.BPMNCallConversation')}</Switch.Item>
</Dropdown>
</section>
</div>
);
}

/**
* Rename the conversation
* @param id The ID of the conversation that should be renamed
*/
private rename = (id: string) => (value: string) => {
this.props.update(id, { name: value });
};

/**
* Change the type of the conversation
* @param id The ID of the conversation whose type should be changed
*/
private changeConversationType = (id: string) => (value: string) => {
this.props.update<BPMNConversation>(id, { conversationType: value as BPMNConversationType });
};

/**
* Delete a conversation
* @param id The ID of the conversation that should be deleted
*/
private delete = (id: string) => () => {
this.props.delete(id);
};
}

export const BPMNConversationUpdate = enhance(BPMNConversationUpdateComponent);
47 changes: 47 additions & 0 deletions src/main/packages/bpmn/bpmn-conversation/bpmn-conversation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { BPMNElementType } from '..';
import { ILayer } from '../../../services/layouter/layer';
import { ILayoutable } from '../../../services/layouter/layoutable';
import { IUMLElement, UMLElement } from '../../../services/uml-element/uml-element';
import { UMLElementType } from '../../uml-element-type';
import { UMLElementFeatures } from '../../../services/uml-element/uml-element-features';
import { IBoundary } from '../../../utils/geometry/boundary';
import { DeepPartial } from 'redux';
import { assign } from '../../../utils/fx/assign';
import * as Apollon from '../../../typings';

export type BPMNConversationType = 'default' | 'call';

export class BPMNConversation extends UMLElement {
static features: UMLElementFeatures = { ...UMLElement.features, resizable: false };
static defaultConversationType: BPMNConversationType = 'default';

type: UMLElementType = BPMNElementType.BPMNConversation;
bounds: IBoundary = { ...this.bounds, width: 40, height: 40 };
conversationType: BPMNConversationType;

constructor(values?: DeepPartial<BPMNConversation>) {
super(values);
assign<IUMLElement>(this, values);
this.conversationType = values?.conversationType || BPMNConversation.defaultConversationType;
}

serialize(children?: UMLElement[]): Apollon.BPMNConversation {
return {
...super.serialize(),
type: this.type as keyof typeof BPMNElementType,
conversationType: this.conversationType,
};
}

deserialize<T extends Apollon.UMLModelElement>(
values: T & { conversationType?: BPMNConversationType },
children?: Apollon.UMLModelElement[],
): void {
super.deserialize(values, children);
this.conversationType = values.conversationType || BPMNConversation.defaultConversationType;
}

render(canvas: ILayer): ILayoutable[] {
return [this];
}
}
Loading

0 comments on commit 491b6d2

Please sign in to comment.