1
- import { useEffect , useCallback } from 'react' ;
1
+ import { useCallback , useEffect } from 'react' ;
2
2
import { useBpmnContext } from '../contexts/BpmnContext' ;
3
3
import { BpmnModelerInstance } from '../utils/bpmnModeler/BpmnModelerInstance' ;
4
4
import { useBpmnConfigPanelFormContext } from '../contexts/BpmnConfigPanelContext' ;
@@ -7,53 +7,56 @@ import type { TaskEvent } from '../types/TaskEvent';
7
7
import type { SelectionChangedEvent } from '../types/SelectionChangeEvent' ;
8
8
import { getBpmnEditorDetailsFromBusinessObject } from '../utils/bpmnObjectBuilders' ;
9
9
import { useStudioRecommendedNextActionContext } from '@studio/components' ;
10
+ import type Modeler from 'bpmn-js/lib/Modeler' ;
10
11
11
12
// Wrapper around bpmn-js to Reactify it
12
13
13
14
export type UseBpmnEditorResult = ( div : HTMLDivElement ) => void ;
14
15
15
16
export const useBpmnEditor = ( ) : UseBpmnEditorResult => {
16
- const { getUpdatedXml, bpmnXml , modelerRef , setBpmnDetails } = useBpmnContext ( ) ;
17
+ const { getUpdatedXml, setBpmnDetails } = useBpmnContext ( ) ;
17
18
const { metadataFormRef, resetForm } = useBpmnConfigPanelFormContext ( ) ;
18
19
const { addAction } = useStudioRecommendedNextActionContext ( ) ;
19
20
20
21
const { saveBpmn, onProcessTaskAdd, onProcessTaskRemove } = useBpmnApiContext ( ) ;
21
22
22
- const handleCommandStackChanged = async ( ) => {
23
- saveBpmn ( await getUpdatedXml ( ) , metadataFormRef . current || null ) ;
23
+ const handleCommandStackChanged = useCallback ( async ( ) => {
24
+ const xml = await getUpdatedXml ( ) ;
25
+ saveBpmn ( xml , metadataFormRef . current || null ) ;
24
26
resetForm ( ) ;
25
- } ;
26
-
27
- const handleShapeAdd = ( taskEvent : TaskEvent ) : void => {
28
- const bpmnDetails = getBpmnEditorDetailsFromBusinessObject ( taskEvent ?. element ?. businessObject ) ;
29
- onProcessTaskAdd ( {
30
- taskEvent,
31
- taskType : bpmnDetails . taskType ,
32
- } ) ;
33
- if (
34
- bpmnDetails . taskType === 'data' ||
35
- bpmnDetails . taskType === 'payment' ||
36
- bpmnDetails . taskType === 'signing'
37
- )
38
- addAction ( bpmnDetails . id ) ;
39
- } ;
40
-
41
- const handleShapeRemove = ( taskEvent : TaskEvent ) : void => {
42
- const bpmnDetails = getBpmnEditorDetailsFromBusinessObject ( taskEvent ?. element ?. businessObject ) ;
43
- onProcessTaskRemove ( {
44
- taskEvent,
45
- taskType : bpmnDetails . taskType ,
46
- } ) ;
47
- } ;
48
-
49
- const handleSelectionChange = ( selectionEvent : SelectionChangedEvent ) : void => {
50
- if ( selectionEvent . newSelection . length !== 1 ) {
51
- setBpmnDetails ( null ) ;
52
- return ;
53
- }
54
- const selectedElement = selectionEvent . newSelection [ 0 ] ;
55
- updateBpmnDetails ( selectedElement ) ;
56
- } ;
27
+ } , [ saveBpmn , resetForm , metadataFormRef , getUpdatedXml ] ) ;
28
+
29
+ const handleShapeAdd = useCallback (
30
+ async ( taskEvent : TaskEvent ) : Promise < void > => {
31
+ const bpmnDetails = getBpmnEditorDetailsFromBusinessObject (
32
+ taskEvent ?. element ?. businessObject ,
33
+ ) ;
34
+ onProcessTaskAdd ( {
35
+ taskEvent,
36
+ taskType : bpmnDetails . taskType ,
37
+ } ) ;
38
+ if (
39
+ bpmnDetails . taskType === 'data' ||
40
+ bpmnDetails . taskType === 'payment' ||
41
+ bpmnDetails . taskType === 'signing'
42
+ )
43
+ addAction ( bpmnDetails . id ) ;
44
+ } ,
45
+ [ addAction , onProcessTaskAdd ] ,
46
+ ) ;
47
+
48
+ const handleShapeRemove = useCallback (
49
+ ( taskEvent : TaskEvent ) : void => {
50
+ const bpmnDetails = getBpmnEditorDetailsFromBusinessObject (
51
+ taskEvent ?. element ?. businessObject ,
52
+ ) ;
53
+ onProcessTaskRemove ( {
54
+ taskEvent,
55
+ taskType : bpmnDetails . taskType ,
56
+ } ) ;
57
+ } ,
58
+ [ onProcessTaskRemove ] ,
59
+ ) ;
57
60
58
61
const updateBpmnDetails = useCallback (
59
62
( element : any ) => {
@@ -66,52 +69,73 @@ export const useBpmnEditor = (): UseBpmnEditorResult => {
66
69
[ setBpmnDetails ] ,
67
70
) ;
68
71
69
- const initializeEditor = async ( ) => {
70
- if ( ! modelerRef . current ) return ;
71
- try {
72
- await modelerRef . current . importXML ( bpmnXml ) ;
73
- const canvas : any = modelerRef . current . get ( 'canvas' ) ;
74
- canvas . zoom ( 'fit-viewport' ) ;
75
- } catch ( exception ) {
76
- console . log ( 'An error occurred while rendering the viewer:' , exception ) ;
77
- }
78
- } ;
79
-
80
- const initializeBpmnChanges = ( ) => {
81
- modelerRef . current . on ( 'commandStack.changed' , async ( ) : Promise < void > => {
82
- await handleCommandStackChanged ( ) ;
83
- } ) ;
84
- modelerRef . current . on ( 'shape.added' , ( taskEvent : TaskEvent ) : void => {
85
- handleShapeAdd ( taskEvent ) ;
86
- } ) ;
87
- modelerRef . current . on ( 'shape.remove' , ( taskEvent : TaskEvent ) : void => {
88
- handleShapeRemove ( taskEvent ) ;
89
- } ) ;
90
- modelerRef . current . on ( 'selection.changed' , ( selectionEvent : SelectionChangedEvent ) : void => {
91
- handleSelectionChange ( selectionEvent ) ;
92
- } ) ;
93
- } ;
94
-
95
- const canvasRef = useCallback ( ( div : HTMLDivElement ) => {
96
- if ( modelerRef . current ) return ;
97
-
98
- modelerRef . current = BpmnModelerInstance . getInstance ( div ) ;
99
-
100
- initializeEditor ( ) . then ( ( ) => {
101
- // Wait for the initializeEditor to be initialized before attaching event listeners, to avoid trigger add.shape events on first draw
102
- initializeBpmnChanges ( ) ;
103
- } ) ;
104
-
105
- // eslint-disable-next-line react-hooks/exhaustive-deps
106
- } , [ ] ) ; // Missing dependencies are not added to avoid getModeler to be called multiple times
72
+ const handleSelectionChange = useCallback (
73
+ ( selectionEvent : SelectionChangedEvent ) : void => {
74
+ if ( selectionEvent . newSelection . length !== 1 ) {
75
+ setBpmnDetails ( null ) ;
76
+ return ;
77
+ }
78
+ const selectedElement = selectionEvent . newSelection [ 0 ] ;
79
+ updateBpmnDetails ( selectedElement ) ;
80
+ } ,
81
+ [ setBpmnDetails , updateBpmnDetails ] ,
82
+ ) ;
107
83
108
- useEffect ( ( ) => {
109
- // Destroy the modeler instance when the component is unmounted
110
- return ( ) => {
111
- BpmnModelerInstance . destroyInstance ( ) ;
112
- } ;
113
- // eslint-disable-next-line react-hooks/exhaustive-deps
114
- } , [ ] ) ;
115
-
116
- return canvasRef ;
84
+ useModelerEventListener < void > ( 'commandStack.changed' , handleCommandStackChanged ) ;
85
+ useModelerEventListener < TaskEvent > ( 'shape.added' , handleShapeAdd ) ;
86
+ useModelerEventListener < TaskEvent > ( 'shape.remove' , handleShapeRemove ) ;
87
+ useModelerEventListener < SelectionChangedEvent > ( 'selection.changed' , handleSelectionChange ) ;
88
+
89
+ return useEditorCallback ( ) ;
117
90
} ;
91
+
92
+ function useModelerEventListener < Event > ( eventName : string , callback : ( event : Event ) => void ) : void {
93
+ const { modelerRef, isInitialized } = useBpmnContext ( ) ;
94
+
95
+ useEffect ( ( ) => {
96
+ if ( isInitialized ) {
97
+ const modeler = modelerRef . current ;
98
+ modeler . on ( eventName , callback ) ;
99
+ return ( ) => modeler . off ( eventName , callback ) ;
100
+ }
101
+ } , [ isInitialized , eventName , callback , modelerRef ] ) ;
102
+ }
103
+
104
+ function useEditorCallback ( ) : ( div : HTMLDivElement ) => void {
105
+ const { initialBpmnXml, modelerRef, setIsInitialized } = useBpmnContext ( ) ;
106
+
107
+ const initialize = useCallback (
108
+ ( div : HTMLDivElement ) => {
109
+ const modeler = BpmnModelerInstance . getInstance ( div ) ;
110
+ if ( ! modelerRef . current ) {
111
+ modelerRef . current = modeler ;
112
+ initializeEditor ( modeler , initialBpmnXml ) . then ( ( ) => setIsInitialized ( true ) ) ;
113
+ }
114
+ } ,
115
+ [ setIsInitialized , modelerRef , initialBpmnXml ] ,
116
+ ) ;
117
+
118
+ const cleanUp = useCallback ( ( ) => {
119
+ setIsInitialized ( false ) ;
120
+ modelerRef . current = null ;
121
+ BpmnModelerInstance . destroyInstance ( ) ;
122
+ } , [ setIsInitialized , modelerRef ] ) ;
123
+
124
+ return useCallback (
125
+ ( div : HTMLDivElement ) => {
126
+ initialize ( div ) ;
127
+ if ( div === null ) cleanUp ( ) ; // Todo: Change this to a return function when we have upgraded to React 19. https://react.dev/reference/react-dom/components/common#react-19-added-cleanup-functions-for-ref-callbacks
128
+ } ,
129
+ [ initialize , cleanUp ] ,
130
+ ) ;
131
+ }
132
+
133
+ async function initializeEditor ( modeler : Modeler , bpmnXml : string ) : Promise < void > {
134
+ try {
135
+ await modeler . importXML ( bpmnXml ) ;
136
+ const canvas = modeler . get < { zoom : ( string : string ) => void } > ( 'canvas' ) ;
137
+ canvas . zoom ( 'fit-viewport' ) ;
138
+ } catch ( exception ) {
139
+ console . log ( 'An error occurred while rendering the viewer:' , exception ) ;
140
+ }
141
+ }
0 commit comments