From e20671a8afea130d8045f6b84902b4e21de5214f Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Thu, 19 Mar 2026 14:49:18 +0100 Subject: [PATCH 1/2] Fixed: when the import modal is opened multiple times the bpmn preview works only in the first time; added support for exporting/importing empty folders --- .../components/bpmn-canvas.tsx | 2 +- .../components/bpmn-viewer.tsx | 15 ++- .../components/process-modal.tsx | 101 +++++++++++++----- .../lib/data/processes.tsx | 11 ++ .../lib/process-export/export-preparation.ts | 43 ++++++++ 5 files changed, 140 insertions(+), 32 deletions(-) diff --git a/src/management-system-v2/components/bpmn-canvas.tsx b/src/management-system-v2/components/bpmn-canvas.tsx index 3f144b142..8f908f201 100644 --- a/src/management-system-v2/components/bpmn-canvas.tsx +++ b/src/management-system-v2/components/bpmn-canvas.tsx @@ -399,7 +399,7 @@ const BPMNCanvas = forwardRef( return () => resizeObserver.disconnect(); }, [resizeWithContainer]); - return
; + return
; }, ); diff --git a/src/management-system-v2/components/bpmn-viewer.tsx b/src/management-system-v2/components/bpmn-viewer.tsx index 8280a1745..0aa712554 100644 --- a/src/management-system-v2/components/bpmn-viewer.tsx +++ b/src/management-system-v2/components/bpmn-viewer.tsx @@ -60,6 +60,7 @@ const BPMNViewer: FC = ({ }; type LazyLoadingBPMNViewerProps = BPMNViewerProps & { + visible?: boolean; fallback?: React.ReactNode; }; @@ -68,15 +69,21 @@ export const LazyBPMNViewer: FC = ({ ...props }) => { const ViewerContainerRef = useRef(null); - const visible = useLazyRendering(ViewerContainerRef); + const internalVisible = useLazyRendering(ViewerContainerRef); + + const _visible = props.visible !== undefined ? props.visible : internalVisible; + const _props = { ...props, visible: undefined }; return ( <> -
- {visible /* This ensures, that only elements, that are visible or close to beeing visible are rendered -> reduces requests for bpmn/xml */ ? ( +
+ {_visible /* This ensures, that only elements, that are visible or close to beeing visible are rendered -> reduces requests for bpmn/xml */ ? ( {/* Prevent sequential rendering/ get from showing the Icon-list */} - + ) : ( fallback diff --git a/src/management-system-v2/components/process-modal.tsx b/src/management-system-v2/components/process-modal.tsx index 0ef4b6b5c..8a57c769a 100644 --- a/src/management-system-v2/components/process-modal.tsx +++ b/src/management-system-v2/components/process-modal.tsx @@ -18,6 +18,7 @@ import { Skeleton, Breadcrumb, } from 'antd'; +import { FolderOutlined } from '@ant-design/icons'; import { MdArrowBackIos, MdArrowForwardIos } from 'react-icons/md'; import { UserError } from '@/lib/user-error'; import { useAddControlCallback } from '@/lib/controls-store'; @@ -31,6 +32,7 @@ import dynamic from 'next/dynamic'; import '@toast-ui/editor/dist/toastui-editor.css'; import type { Editor as EditorClass } from '@toast-ui/react-editor'; +import { isDummyFolderProcess } from '@/lib/process-export/export-preparation'; const TextEditor = dynamic(() => import('@/components/text-editor'), { ssr: false, loading: () => , @@ -38,7 +40,7 @@ const TextEditor = dynamic(() => import('@/components/text-editor'), { export type ProcessModalMode = 'create' | 'edit' | 'copy' | 'import'; -type ProcessModalProps = { +type ProcessModalProps = { open: boolean; title: string; okText?: string; @@ -52,6 +54,7 @@ type ProcessModalProps = { const ProcessModal = < T extends { + id?: string; name: string; description: string; userDefinedId?: string; @@ -214,6 +217,8 @@ const ProcessModal = < { level: 2, blocking: open, dependencies: [open] }, ); + const [fullyOpen, setFullyOpen] = useState(false); + const renderFormContent = () => { if (!initialData) { return ; @@ -255,13 +260,38 @@ const ProcessModal = < > {initialData.map((process, index) => ( - {process.bpmn && ( - <> - - - - )} - + <> + {process.bpmn && ( + <> + {isDummyFolderProcess(process) ? ( +
+ +
+ ) : ( + + )} + + + )} + +
))} @@ -309,6 +339,7 @@ const ProcessModal = < flexDirection: 'column', }, }} + afterOpenChange={(open) => setFullyOpen(open)} >
{nameCollisions.length > 0 && showCollisions && ( @@ -395,6 +426,7 @@ type ProcessInputsProps = { index: number; initialName?: string; readonly?: boolean; + isFolder?: boolean; }; const ProcessInputs = ({ index, initialName, readonly = false }: ProcessInputsProps) => { @@ -486,7 +518,7 @@ const ProcessInputs = ({ index, initialName, readonly = false }: ProcessInputsPr ); }; -const ProcessInputsImport = ({ index, readonly = false }: ProcessInputsProps) => { +const ProcessInputsImport = ({ index, readonly = false, isFolder }: ProcessInputsProps) => { const instance = Form.useFormInstance(); const data = instance.getFieldsValue()[index]; @@ -497,6 +529,13 @@ const ProcessInputsImport = ({ index, readonly = false }: ProcessInputsProps) => + {isFolder && ( + + )} {!!data?.folderPath && ( @@ -509,24 +548,32 @@ const ProcessInputsImport = ({ index, readonly = false }: ProcessInputsProps) => )} - - - - - - - - - - + {!isFolder && ( + <> + + + + + + + + + + + + )} ); }; diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx index df3894cb2..b0cbc83dd 100644 --- a/src/management-system-v2/lib/data/processes.tsx +++ b/src/management-system-v2/lib/data/processes.tsx @@ -8,6 +8,7 @@ import { generateScriptTaskFileName, generateStartFormFileName, generateUserTaskFileName, + getDefinitionsId, getDefinitionsVersionInformation, getElementsByTagName, setDefinitionsName, @@ -66,6 +67,7 @@ import { getFolderById, getRootFolder } from './db/folders'; import { truthyFilter } from '../typescript-utils'; import { createFolder, getFolder, getFolderContents } from './folders'; import Ability from '../ability/abilityHelper'; +import { isDummyFolderProcess } from '../process-export/export-preparation'; // Import necessary functions from processModule @@ -275,6 +277,12 @@ export const addProcesses = async ( value.folderId = folder.id; } + if (isDummyFolderProcess(value)) { + // if the process represents a placeholder for importing empty folders then skip adding the + // process after the folder structure was created + continue; + } + // bpmn prop gets deleted in addProcess() const process = await _addProcess({ ...newProcess, folderId: value.folderId }); @@ -395,6 +403,9 @@ export const importProcesses = async (processData: ProcessData[], spaceId: strin return importedProcesses; } + // filter out dummy processes that were included in the import to generate empty folders + processData = processData.filter((p) => !isDummyFolderProcess(p)); + for (let idx = 0; idx < importedProcesses.length; idx++) { const process = importedProcesses[idx]; const artefacts = processData[idx].artefacts; diff --git a/src/management-system-v2/lib/process-export/export-preparation.ts b/src/management-system-v2/lib/process-export/export-preparation.ts index 75219e58c..04e6b0532 100644 --- a/src/management-system-v2/lib/process-export/export-preparation.ts +++ b/src/management-system-v2/lib/process-export/export-preparation.ts @@ -21,6 +21,9 @@ import { toBpmnXml, getAllElements, getStartFormFileNameMapping, + initXml, + setDefinitionsId, + setDefinitionsName, } from '@proceed/bpmn-helper'; import { asyncMap, asyncFilter } from '../helpers/javascriptHelpers'; @@ -250,6 +253,30 @@ async function ensureProcessInfo( isImport = false, ability?: Ability, ) { + if (definitionId.startsWith(dummyProcessId)) { + let bpmn = initXml(); + bpmn = (await setDefinitionsId(bpmn, definitionId)) as string; + bpmn = (await setDefinitionsName(bpmn, 'Dummy Process')) as string; + exportData[definitionId] = { + definitionName: 'Dummy Process', + folderPath, + isImport, + versions: { + latest: { + bpmn, + isImport, + layers: [], + imports: [], + }, + }, + scriptTasks: [], + userTasks: [], + images: [], + }; + + return; + } + if (!exportData[definitionId]) { const process = await getProcess(definitionId, spaceId, undefined, ability); @@ -295,6 +322,12 @@ async function ensureProcessInfo( } } +export const dummyProcessId = '___empty_dummy_process___'; + +export function isDummyFolderProcess(input: { id?: string }) { + return 'id' in input && input.id?.startsWith(dummyProcessId); +} + /** * Gets the data that is needed to export all the requested processes with the given options * @@ -321,6 +354,16 @@ export async function prepareExport( if (isUserErrorResponse(folder)) throw folder.error.message; if (isUserErrorResponse(content)) throw content.error.message; + // make sure empty folders are exported by placing a dummy process into the folder + if (!content.length) { + return [ + { + definitionId: `${dummyProcessId}_${path.split('/').join('_')}_${folder.name}`, + folderPath: path + '/' + folder.name, + }, + ]; + } + return ( await asyncMap(content, async (entry) => { if (entry.type !== 'folder') { From 0af19a36c84272acab3212a8022bdea4f1a0086f Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Fri, 20 Mar 2026 11:14:48 +0100 Subject: [PATCH 2/2] Changed the name of the placeholder process folders in export zip files --- .../lib/process-export/export-preparation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/management-system-v2/lib/process-export/export-preparation.ts b/src/management-system-v2/lib/process-export/export-preparation.ts index 04e6b0532..324da46c3 100644 --- a/src/management-system-v2/lib/process-export/export-preparation.ts +++ b/src/management-system-v2/lib/process-export/export-preparation.ts @@ -256,9 +256,9 @@ async function ensureProcessInfo( if (definitionId.startsWith(dummyProcessId)) { let bpmn = initXml(); bpmn = (await setDefinitionsId(bpmn, definitionId)) as string; - bpmn = (await setDefinitionsName(bpmn, 'Dummy Process')) as string; + bpmn = (await setDefinitionsName(bpmn, 'Placeholder Process To Keep The Folder')) as string; exportData[definitionId] = { - definitionName: 'Dummy Process', + definitionName: 'Placeholder Process To Keep The Folder', folderPath, isImport, versions: {