Skip to content

Commit

Permalink
Ms2/modeler selection export (#199)
Browse files Browse the repository at this point in the history
* Include elements in copy that are not directly selected but where a predecessor is selected

* Merge branch 'main' of github.com:PROCEED-Labs/proceed into ms2/modeler-selection-export

* Added vscode debug config for new MS frontend; Fixed: trying to get the DI of an element in a bpmn with an empty collapsed subprocess leads to an error

* Handling selected elements when exporting a process from the modeler and the respective export option is selected. Related to #144

* When exporting from the modeler only export the currently open layer and if collapsed subprocesses or imports should be exported only those nested under the current layer. Related to #144

* Some minor cleanup in the code
  • Loading branch information
jjoderis authored Dec 19, 2023
1 parent f476e0c commit 7b4a7c4
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 148 deletions.
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@
"webpack:///src/*.vue": "${webRoot}/*.vue"
}
},
{
"name": "MS2: Attach to web client",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/src/management-system-v2"
},
{
"type": "chrome",
"request": "attach",
Expand Down
4 changes: 3 additions & 1 deletion src/helper-modules/bpmn-helper/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ function getChildren(travObj) {
'imports',
'extensionElements',
'participants',
'laneSets',
'lanes',
];

const allChildren = childNodeTypes
Expand Down Expand Up @@ -120,7 +122,7 @@ function getElementDI(element, definitions) {
}

for (const diagram of definitions.diagrams) {
for (const planeElement of diagram.plane.planeElement) {
for (const planeElement of diagram.plane.planeElement || []) {
if (planeElement.bpmnElement === element) {
return planeElement;
}
Expand Down
33 changes: 32 additions & 1 deletion src/management-system-v2/components/modeler-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import React, { useEffect, useState } from 'react';

import type ElementRegistry from 'diagram-js/lib/core/ElementRegistry';
import type CommandStack from 'diagram-js/lib/command/CommandStack';
import type Selection from 'diagram-js/lib/features/selection/Selection';
import type Canvas from 'diagram-js/lib/core/Canvas';

import { is as bpmnIs } from 'bpmn-js/lib/util/ModelUtil';

import { Tooltip, Button, Space } from 'antd';
import { Toolbar, ToolbarGroup } from './toolbar';
Expand Down Expand Up @@ -40,6 +44,8 @@ const ModelerToolbar: React.FC<ModelerToolbarProps> = ({ onOpenXmlEditor }) => {

const [showPropertiesPanel, setShowPropertiesPanel] = useState(false);
const [showProcessExportModal, setShowProcessExportModal] = useState(false);
const [elementsSelectedForExport, setElementsSelectedForExport] = useState<string[]>([]);
const [rootLayerIdForExport, setRootLayerIdForExport] = useState<string | undefined>(undefined);

const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
Expand Down Expand Up @@ -108,6 +114,22 @@ const ModelerToolbar: React.FC<ModelerToolbarProps> = ({ onOpenXmlEditor }) => {
};

const handleProcessExportModalToggle = async () => {
if (!showProcessExportModal && modeler?.get) {
// provide additional information for the export that is used if the user decides to only export selected elements (also controls if the option is given in the first place)
const selectedElementIds = (modeler.get('selection') as Selection).get().map(({ id }) => id);
setElementsSelectedForExport(selectedElementIds);
// provide additional information for the export so only the parts of the process that can be reached from the currently open layer are exported
const currentRootElement = (modeler.get('canvas') as Canvas).getRootElement();
setRootLayerIdForExport(
bpmnIs(currentRootElement, 'bpmn:SubProcess')
? currentRootElement.businessObject?.id
: undefined,
);
} else {
setElementsSelectedForExport([]);
setRootLayerIdForExport(undefined);
}

setShowProcessExportModal(!showProcessExportModal);
};

Expand Down Expand Up @@ -165,12 +187,21 @@ const ModelerToolbar: React.FC<ModelerToolbarProps> = ({ onOpenXmlEditor }) => {
)}
</Toolbar>
<ProcessExportModal
open={showProcessExportModal}
processes={
showProcessExportModal
? [{ definitionId: processId as string, processVersion: selectedVersion || undefined }]
? [
{
definitionId: processId as string,
processVersion: selectedVersion || undefined,
selectedElements: elementsSelectedForExport,
rootSubprocessLayerId: rootLayerIdForExport,
},
]
: []
}
onClose={() => setShowProcessExportModal(false)}
giveSelectionOption={!!elementsSelectedForExport.length}
/>
</>
);
Expand Down
6 changes: 3 additions & 3 deletions src/management-system-v2/components/modeler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,16 @@ const Modeler = ({ processBpmn, versionName, process, ...divProps }: ModelerProp
setEditingDisabled(false);
}

// allow keyboard shortcuts like copy (strg+c) and paste (strg+v) etc.
// allow keyboard shortcuts like copy (ctrl+c) and paste (ctrl+v) etc.
(modeler.current.get('keyboard') as any).bind(document);

// create a custom copy behaviour where the whole process or selected parts can be copied to the clipboard as an image
(modeler.current.get('keyboard') as any).addListener(
async (_: any, events: { keyEvent: KeyboardEvent }) => {
(_: any, events: { keyEvent: KeyboardEvent }) => {
const { keyEvent } = events;
// handle the copy shortcut
if (keyEvent.ctrlKey && keyEvent.key === 'c' && modeler.current) {
await copyProcessImage(modeler.current);
copyProcessImage(modeler.current);
}
},
'keyboard.keyup',
Expand Down
169 changes: 96 additions & 73 deletions src/management-system-v2/components/process-export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import type { CheckboxValueType } from 'antd/es/checkbox/Group';

import { exportProcesses } from '@/lib/process-export';
import { ProcessExportOptions } from '@/lib/process-export/export-preparation';
import { ProcessExportOptions, ExportProcessInfo } from '@/lib/process-export/export-preparation';

const exportTypeOptions = [
{ label: 'BPMN', value: 'bpmn' },
Expand All @@ -25,78 +25,97 @@ const exportTypeOptions = [
{ label: 'PNG', value: 'png' },
];

const exportSubOptions = {
bpmn: [
{
label: 'with artefacts',
value: 'artefacts',
tooltip:
'Also export html and images used in User-Tasks and images used for other process elements',
},
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
],
pdf: [
{
label: 'with meta data',
value: 'metaData',
tooltip: 'Add process meta information to each page (process name, version, etc.)',
},
{
label: 'A4 pages',
value: 'a4',
tooltip: 'Use A4 format for all pages (Scales down the process image if necessary)',
},
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
{
label: 'with collapsed subprocesses',
value: 'subprocesses',
tooltip: 'Also export content of all collapsed subprocesses',
},
],
svg: [
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
{
label: 'with collapsed subprocesses',
value: 'subprocesses',
tooltip: 'Also export content of all collapsed subprocesses',
},
],
png: [
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
{
label: 'with collapsed subprocesses',
value: 'subprocesses',
tooltip: 'Also export content of all collapsed subprocesses',
},
],
};
function getSubOptions(giveSelectionOption?: boolean) {
const exportSubOptions = {
bpmn: [
{
label: 'with artefacts',
value: 'artefacts',
tooltip:
'Also export html and images used in User-Tasks and images used for other process elements',
},
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
],
pdf: [
{
label: 'with meta data',
value: 'metaData',
tooltip: 'Add process meta information to each page (process name, version, etc.)',
},
{
label: 'A4 pages',
value: 'a4',
tooltip: 'Use A4 format for all pages (Scales down the process image if necessary)',
},
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
{
label: 'with collapsed subprocesses',
value: 'subprocesses',
tooltip: 'Also export content of all collapsed subprocesses',
},
],
svg: [
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
{
label: 'with collapsed subprocesses',
value: 'subprocesses',
tooltip: 'Also export content of all collapsed subprocesses',
},
],
png: [
{
label: 'with referenced processes',
value: 'imports',
tooltip: 'Also export all referenced processes used in call-activities',
},
{
label: 'with collapsed subprocesses',
value: 'subprocesses',
tooltip: 'Also export content of all collapsed subprocesses',
},
],
};

const selectionOption = {
label: 'limit to selection',
value: 'onlySelection',
tooltip:
'Exclude elements from the image(s) that are not selected and not inside a selected element',
};

if (giveSelectionOption) {
exportSubOptions.png.push(selectionOption);
exportSubOptions.svg.push(selectionOption);
exportSubOptions.pdf.push(selectionOption);
}

return exportSubOptions;
}

type ProcessExportModalProps = {
processes: { definitionId: string; processVersion?: number | string }[]; // the processes to export; also used to decide if the modal should be opened
processes: ExportProcessInfo; // the processes to export
onClose: () => void;
open: boolean;
giveSelectionOption?: boolean; // if the user can select to limit the export to elements selected in the modeler (only usable in the modeler)
};

const ProcessExportModal: React.FC<ProcessExportModalProps> = ({
processes = [],
onClose,
open,
giveSelectionOption,
}) => {
const [selectedType, setSelectedType] = useState<ProcessExportOptions['type']>();
const [selectedOptions, setSelectedOptions] = useState<CheckboxValueType[]>(['metaData']);
Expand All @@ -113,6 +132,7 @@ const ProcessExportModal: React.FC<ProcessExportModalProps> = ({

const handleClose = () => {
setIsExporting(false);
setSelectedOptions(selectedOptions.filter((el) => el !== 'onlySelection'));
onClose();
};

Expand All @@ -121,12 +141,13 @@ const ProcessExportModal: React.FC<ProcessExportModalProps> = ({
await exportProcesses(
{
type: selectedType!,
artefacts: selectedOptions.some((el) => el === 'artefacts'),
subprocesses: selectedOptions.some((el) => el === 'subprocesses'),
imports: selectedOptions.some((el) => el === 'imports'),
metaData: selectedOptions.some((el) => el === 'metaData'),
a4: selectedOptions.some((el) => el === 'a4'),
artefacts: selectedOptions.includes('artefacts'),
subprocesses: selectedOptions.includes('subprocesses'),
imports: selectedOptions.includes('imports'),
metaData: selectedOptions.includes('metaData'),
a4: selectedOptions.includes('a4'),
scaling: pngScalingFactor,
exportSelectionOnly: selectedOptions.includes('onlySelection'),
},
processes,
);
Expand Down Expand Up @@ -154,11 +175,13 @@ const ProcessExportModal: React.FC<ProcessExportModalProps> = ({
style={{ width: '100%' }}
>
<Space direction="vertical">
{(selectedType ? exportSubOptions[selectedType] : []).map(({ label, value, tooltip }) => (
<Tooltip placement="left" title={tooltip} key={label}>
<Checkbox value={value}>{label}</Checkbox>
</Tooltip>
))}
{(selectedType ? getSubOptions(giveSelectionOption)[selectedType] : []).map(
({ label, value, tooltip }) => (
<Tooltip placement="left" title={tooltip} key={label}>
<Checkbox value={value}>{label}</Checkbox>
</Tooltip>
),
)}
</Space>
</Checkbox.Group>
{selectedType === 'png' && (
Expand Down
Loading

0 comments on commit 7b4a7c4

Please sign in to comment.