Skip to content

Commit

Permalink
Multi Process Export (New MS) (#115)
Browse files Browse the repository at this point in the history
* select box styling, table categories button added, functionality tba

* select bar layout wip

* added first version of a breadcrumb

* styling wip

* Breadcrumb version and process select

* fuzzy search, dropdown for columns, deselect for multirow

* add comment for fuzzy search

* changed colour breadcrumb, added latest changes to modeler (jj), Menu (sider) updated

* Adjusted Modeler height, added Preview-Viewer Co-authored-by: winniel24 <[email protected]> Co-authored-by: jjoderis <[email protected]> Co-authored-by: Lucas <[email protected]>

* Made previewer height adjustable

* Added export of a single process as bpmn, svg or pdf from the process modeler

* Added the process export modal to the processes view in addition to the process editor view

* Ran prettier

* Removed unnecessary code

* Added jszip as a dependency

* Added option to export multiple processes at once

---------

Co-authored-by: winnie <[email protected]>
Co-authored-by: LaMaLein <[email protected]>
Co-authored-by: LaMaLein <[email protected]>
  • Loading branch information
4 people authored Oct 11, 2023
1 parent fc491d4 commit bee2351
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 73 deletions.
23 changes: 8 additions & 15 deletions src/management-system-v2/components/modeler-toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';

import type ElementRegistry from 'diagram-js/lib/core/ElementRegistry';

Expand All @@ -20,13 +20,12 @@ import { SvgXML, SvgShare } from '@/components/svg';
import PropertiesPanel from './properties-panel';

import useModelerStateStore from '@/lib/use-modeler-state-store';
import { useParams } from 'next/navigation';
import { useParams, useSearchParams } from 'next/navigation';

import ProcessExportModal from './process-export';

import { createNewProcessVersion } from '@/lib/helpers';
import VersionCreationButton from './version-creation-button';
import { useGetAsset } from '@/lib/fetch-data';

type ModelerToolbarProps = {
onOpenXmlEditor: () => void;
Expand All @@ -45,14 +44,6 @@ const ModelerToolbar: React.FC<ModelerToolbarProps> = ({ onOpenXmlEditor }) => {
// const [index, setIndex] = useState(0);
const { processId } = useParams();

const {
isSuccess,
data: processData,
refetch: refetchProcess,
} = useGetAsset('/process/{definitionId}', {
params: { path: { definitionId: processId as string } },
});

let selectedElement;

if (modeler) {
Expand All @@ -75,7 +66,6 @@ const ModelerToolbar: React.FC<ModelerToolbarProps> = ({ onOpenXmlEditor }) => {
values.versionName,
values.versionDescription,
);
refetchProcess();
}
};
const handlePropertiesPanelToggle = () => {
Expand All @@ -86,7 +76,7 @@ const ModelerToolbar: React.FC<ModelerToolbarProps> = ({ onOpenXmlEditor }) => {
setShowProcessExportModal(!showProcessExportModal);
};

const selectedVersion = useModelerStateStore((state) => state.selectedVersion);
const selectedVersion = useSearchParams().get('version');

return (
<>
Expand Down Expand Up @@ -140,9 +130,12 @@ const ModelerToolbar: React.FC<ModelerToolbarProps> = ({ onOpenXmlEditor }) => {
<PropertiesPanel selectedElement={selectedElement} setOpen={setShowPropertiesPanel} />
)} */}
<ProcessExportModal
processId={showProcessExportModal ? (processId as string) : undefined}
processes={
showProcessExportModal
? [{ definitionId: processId as string, processVersion: selectedVersion || undefined }]
: []
}
onClose={() => setShowProcessExportModal(false)}
processVersion={selectedVersion || undefined}
/>
</>
);
Expand Down
27 changes: 5 additions & 22 deletions src/management-system-v2/components/process-export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useState } from 'react';
import { Modal, Checkbox } from 'antd';
import type { CheckboxValueType } from 'antd/es/checkbox/Group';

import { exportBpmn, exportPDF, exportSVG } from '@/lib/process-export';
import { exportProcesses, exportType } from '@/lib/process-export';

const exportTypeOptions = [
{ label: 'BPMN', value: 'bpmn' },
Expand All @@ -12,16 +12,11 @@ const exportTypeOptions = [
];

type ProcessExportModalProps = {
processId?: string; // the id of the process to export; also used to decide if the modal should be opened
processes: { definitionId: string; processVersion?: number | string }[]; // the processes to export; also used to decide if the modal should be opened
onClose: () => void;
processVersion?: number | string;
};

const ProcessExportModal: React.FC<ProcessExportModalProps> = ({
processId,
onClose,
processVersion,
}) => {
const ProcessExportModal: React.FC<ProcessExportModalProps> = ({ processes = [], onClose }) => {
const [selectedTypes, setSelectedTypes] = useState<CheckboxValueType[]>([]);

const handleTypeSelectionChange = (checkedValues: CheckboxValueType[]) => {
Expand All @@ -31,19 +26,7 @@ const ProcessExportModal: React.FC<ProcessExportModalProps> = ({
};

const handleProcessExport = async () => {
switch (selectedTypes[0]) {
case 'bpmn':
await exportBpmn(processId!, processVersion);
break;
case 'pdf':
await exportPDF(processId!, processVersion);
break;
case 'svg':
await exportSVG(processId!, processVersion);
break;
default:
throw 'Unexpected value for process export!';
}
await exportProcesses(processes, selectedTypes[0] as exportType);

onClose();
};
Expand All @@ -52,7 +35,7 @@ const ProcessExportModal: React.FC<ProcessExportModalProps> = ({
<>
<Modal
title="Export selected process"
open={!!processId}
open={!!processes.length}
onOk={handleProcessExport}
onCancel={onClose}
centered
Expand Down
4 changes: 2 additions & 2 deletions src/management-system-v2/components/process-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type ProcessListProps = PropsWithChildren<{
selection: Key[];
setSelection: Dispatch<SetStateAction<Key[]>>;
isLoading?: boolean;
onExportProcess: Dispatch<SetStateAction<string | undefined>>;
onExportProcess: Dispatch<SetStateAction<string[]>>;
}>;

const ColumnHeader = [
Expand Down Expand Up @@ -113,7 +113,7 @@ const ProcessList: FC<ProcessListProps> = ({
<Tooltip placement="top" title={'Export'}>
<ExportOutlined
onClick={() => {
onExportProcess(record.definitionId);
onExportProcess([record.definitionId]);
}}
/>
</Tooltip>
Expand Down
10 changes: 5 additions & 5 deletions src/management-system-v2/components/processes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const Processes: FC = () => {

const [iconView, setIconView] = useState(prefs['icon-view-in-process-list']);

const [exportProcessId, setExportProcessId] = useState<string | undefined>();
const [exportProcessIds, setExportProcessIds] = useState<string[]>([]);

const actionBar = (
<>
Expand All @@ -72,7 +72,7 @@ const Processes: FC = () => {
<Tooltip placement="top" title={'Export'}>
<ExportOutlined
onClick={() => {
setExportProcessId(selectedRowKeys[0].toString());
setExportProcessIds(selectedRowKeys as string[]);
}}
/>
</Tooltip>
Expand Down Expand Up @@ -180,16 +180,16 @@ const Processes: FC = () => {
selection={selectedRowKeys}
setSelection={setSelectedRowKeys}
isLoading={isLoading}
onExportProcess={setExportProcessId}
onExportProcess={setExportProcessIds}
/>
)}
</div>
{/* Meta Data Panel */}
<MetaData data={filteredData} selection={selectedRowKeys} triggerRerender={rerenderLists} />
</div>
<ProcessExportModal
processId={exportProcessId}
onClose={() => setExportProcessId(undefined)}
processes={exportProcessIds.map((definitionId) => ({ definitionId }))}
onClose={() => setExportProcessIds([])}
/>
</>
);
Expand Down
80 changes: 71 additions & 9 deletions src/management-system-v2/lib/process-export.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { v4 } from 'uuid';
import { jsPDF } from 'jspdf';
import jsZip from 'jszip';
import 'svg2pdf.js';

import { fetchProcessVersionBpmn, fetchProcess } from './process-queries';
Expand Down Expand Up @@ -28,12 +29,59 @@ async function getProcessData(processId: string, processVersion?: string | numbe
return data;
}

export async function exportBpmn(processId: string, processVersion?: string | number) {
export type exportType = 'bpmn' | 'svg' | 'pdf';

export async function exportProcesses(
processes: { definitionId: string; processVersion?: number | string }[],
type: exportType,
) {
if (processes.length === 1) {
// generate the file and download it
switch (type) {
case 'bpmn':
await exportBpmn(exportFile, processes[0].definitionId, processes[0].processVersion);
break;
case 'svg':
await exportSVG(exportFile, processes[0].definitionId, processes[0].processVersion);
break;
case 'pdf':
await exportPDF(exportFile, processes[0].definitionId, processes[0].processVersion);
break;
}
} else if (processes.length > 1) {
const zip = new jsZip();

for (const { definitionId, processVersion } of processes) {
// add the file to the zip archive
switch (type) {
case 'bpmn':
await exportBpmn(zip.file.bind(zip), definitionId, processVersion);
break;
case 'svg':
await exportSVG(zip.file.bind(zip), definitionId, processVersion);
break;
case 'pdf':
await exportPDF(zip.file.bind(zip), definitionId, processVersion);
break;
}
}
// download the zip archive to the users device
exportFile('PROCEED_Multiple-Processes_bpmn.zip', await zip.generateAsync({ type: 'blob' }));
} else {
throw new Error('Tried exporting without specifying the processes to export!');
}
}

export async function exportBpmn(
dataHandler: (fileName: string, data: Blob) => void,
processId: string,
processVersion?: string | number,
) {
const process = await getProcessData(processId, processVersion);

const bpmnBlob = new Blob([process.bpmn!], { type: 'application/xml' });

exportFile(`${process.definitionName}.bpmn`, bpmnBlob);
dataHandler(`${process.definitionName}.bpmn`, bpmnBlob);
}

async function getSVGFromBPMN(bpmn: string) {
Expand All @@ -54,7 +102,11 @@ async function getSVGFromBPMN(bpmn: string) {
return svg;
}

export async function exportSVG(processId: string, processVersion?: string | number) {
export async function exportSVG(
dataHandler: (fileName: string, data: Blob) => void,
processId: string,
processVersion?: string | number,
) {
const process = await getProcessData(processId, processVersion);

const svg = await getSVGFromBPMN(process.bpmn!);
Expand All @@ -63,10 +115,14 @@ export async function exportSVG(processId: string, processVersion?: string | num
type: 'image/svg+xml',
});

exportFile(`${process.definitionName}.svg`, svgBlob);
dataHandler(`${process.definitionName}.svg`, svgBlob);
}

export async function exportPDF(processId: string, processVersion?: string | number) {
export async function exportPDF(
dataHandler: (fileName: string, data: Blob) => void,
processId: string,
processVersion?: string | number,
) {
const process = await getProcessData(processId, processVersion);

const svg = await getSVGFromBPMN(process.bpmn!);
Expand Down Expand Up @@ -107,25 +163,31 @@ export async function exportPDF(processId: string, processVersion?: string | num
height: pageHeight,
});

await doc.save(`${process.definitionName}.pdf`);
dataHandler(`${process.definitionName}.pdf`, await doc.output('blob'));
}

function exportFile(processName: string, data: Blob) {
/**
* Downloads the file to export on the users device
*
* @param fileName
* @param data
*/
function exportFile(fileName: string, data: Blob) {
const objectURL = URL.createObjectURL(data);

// Creating Anchor Element to trigger download feature
const aLink = document.createElement('a');

// Setting anchor tag properties
aLink.style.display = 'none';
aLink.download = processName;
aLink.download = fileName;
aLink.href = objectURL;

// Setting anchor tag to DOM
document.body.appendChild(aLink);
aLink.click();
document.body.removeChild(aLink);

// Release Object URL, so browser dont keep reference
// Release Object URL, so the browser doesn't keep the reference
URL.revokeObjectURL(objectURL);
}
21 changes: 11 additions & 10 deletions src/management-system-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,26 @@
"singleQuote": true
},
"dependencies": {
"jspdf": "^2.5.1",
"svg2pdf.js": "^2.2.2",
"uuid": "^9.0.0",
"@monaco-editor/react": "4.5.2",
"@casl/ability": "6.5.0",
"@monaco-editor/react": "^4.5.2",
"@proceed/bpmn-helper": "1.0.0",
"@tanstack/react-query": "4.35.7",
"antd": "5.9.4",
"bpmn-js": "13.2.0",
"bpmn-js-differ": "2.0.2",
"classnames": "2.3.2",
"fuse.js": "6.6.2",
"immer": "10.0.3",
"jspdf": "^2.5.1",
"jszip": "^3.10.1",
"monaco-editor": "0.43.0",
"next": "13.5.4",
"openapi-fetch": "0.7.8",
"react": "18.2.0",
"react-dom": "18.2.0",
"zustand": "4.4.2",
"immer": "10.0.3",
"fuse.js": "6.6.2",
"@casl/ability": "6.5.0",
"openapi-fetch": "0.7.8",
"@proceed/bpmn-helper": "1.0.0"
"svg2pdf.js": "^2.2.2",
"uuid": "^9.0.0",
"zustand": "4.4.2"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "4.36.0",
Expand Down
20 changes: 10 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1945,19 +1945,19 @@
resolved "https://registry.yarnpkg.com/@mdi/font/-/font-6.9.96.tgz#c68da7e0895885dd09e60dc08c5ecc0d77f67efb"
integrity sha512-z3QVZStyHVwkDsFR7A7F2PIvZJPWgdSFw4BEEy2Gc9HUN5NfK9mGbjgaYClRcbMWiYEV45srmiYtczmBtCqR8w==

"@monaco-editor/loader@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.3.3.tgz#7f1742bd3cc21c0362a46a4056317f6e5215cfca"
integrity sha512-6KKF4CTzcJiS8BJwtxtfyYt9shBiEv32ateQ9T4UVogwn4HM/uPo9iJd2Dmbkpz8CM6Y0PDUpjnZzCwC+eYo2Q==
"@monaco-editor/loader@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558"
integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==
dependencies:
state-local "^1.0.6"

"@monaco-editor/[email protected]":
version "4.5.2"
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.5.2.tgz#e8cc802203f729b423a998ea6fcb466604d61258"
integrity sha512-emcWu6vg1OpXPiYll4aPOaXe8bwYB4UaaNTwtArFLgMoNGBzRZb2Xn0Bra2HMIFM7QLgs7fCGunHO5LkfT2LBA==
"@monaco-editor/react@^4.5.2":
version "4.6.0"
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119"
integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==
dependencies:
"@monaco-editor/loader" "^1.3.3"
"@monaco-editor/loader" "^1.4.0"

"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
Expand Down Expand Up @@ -13442,7 +13442,7 @@ jsprim@^1.2.2:
object.assign "^4.1.4"
object.values "^1.1.6"

jszip@^3.0.0, jszip@^3.1.0, jszip@^3.4.0:
jszip@^3.0.0, jszip@^3.1.0, jszip@^3.10.1, jszip@^3.4.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
Expand Down

0 comments on commit bee2351

Please sign in to comment.