1
+ import { Logger } from '@nestjs/common' ;
2
+ import { unionBy } from 'lodash' ;
1
3
import { arrayToMap } from './array.to.map' ;
2
4
import { ExcalidrawElement } from '../../excalidraw/types' ;
3
5
import { arrayToMapBy } from './array.to.map.by' ;
4
- import { Logger } from '@nestjs/common' ;
5
6
// import { orderByFractionalIndex, syncInvalidIndices } from './fractionalIndex';
6
7
7
8
const shouldDiscardRemoteElement = (
@@ -97,20 +98,28 @@ export const reconcileElements = (
97
98
// de-duplicate indices
98
99
// const syncedElemented = syncInvalidIndices(orderedElements);
99
100
try {
100
- return orderByPrecedingElement ( reconciledElements ) ;
101
+ return tryOrderByPrecedingElement ( reconciledElements ) ;
101
102
} catch ( error ) {
102
103
logger . warn ( `Element sorting failed with error: '${ error . message } '` ) ;
103
104
return reconciledElements ;
104
105
}
105
106
} ;
106
107
107
- const orderByPrecedingElement = (
108
+ /**
109
+ * Will try to order elements by preceding element.
110
+ * Order is not guaranteed, if there are multiple "first" elements, or a preceding element that does not exist.
111
+ * Returns a half sorted array if there is an element with preceding element that does not exist.
112
+ * @param unOrderedElements
113
+ * @throws Error if there is more than one element with preceding element = '^'
114
+ */
115
+ const tryOrderByPrecedingElement = (
108
116
unOrderedElements : ExcalidrawElement [ ] ,
109
117
) : ExcalidrawElement [ ] | never => {
110
118
// for zero or one element return the same array, as it's already sorted
111
119
if ( unOrderedElements . length < 2 ) {
112
120
return unOrderedElements ;
113
121
}
122
+ // const elementsWithPreceding = unOrderedElements.filter(el.
114
123
// validated there is just one starting element
115
124
const startElements = unOrderedElements . filter (
116
125
( el ) => el . __precedingElement__ === '^' ,
@@ -131,14 +140,24 @@ const orderByPrecedingElement = (
131
140
// the array is starting with element that has no preceding element
132
141
let parentElement = startElements [ 0 ] ;
133
142
orderedElements . push ( parentElement ) ;
134
- // Follow the chain of __precedingElement__
135
- while ( true ) {
143
+ // keep track of visited elements to prevent cycles
144
+ const visitedElements = new Set < string > ( ) ;
145
+ // Follow the chain of __precedingElement__ until we have sorted all
146
+ while ( orderedElements . length != unOrderedElements . length ) {
147
+ // prevent cycles in the chain
148
+ if ( visitedElements . has ( parentElement . id ) ) {
149
+ throw new Error ( 'Cycle detected in __precedingElement__ chain' ) ;
150
+ }
151
+ visitedElements . add ( parentElement . id ) ;
152
+ // a child of a parent, is an element which __precedingElement__ is pointing to the parent
153
+ // is there an element which preceding element is the parent element
136
154
const childElement = elementMapByPrecedingKey . get ( parentElement . id ) ;
137
-
155
+ // there is a parent, which has no child; the chain is broken
138
156
if ( ! childElement ) {
139
- // we have reached the end of the chain
140
- break ;
157
+ // switch both arrays; combine ordered and switch the unordered at the end
158
+ return unionBy ( orderedElements , unOrderedElements , 'id' ) ;
141
159
}
160
+ // the final element is pointing to the one before it (preceding element)
142
161
143
162
orderedElements . push ( childElement ) ;
144
163
parentElement = childElement ;
0 commit comments