11import { MetadataMap , MetadataValue } from '../../core/shared/metadata.models' ;
22import { hasNoValue , hasValue , isEmpty , isNotEmpty } from '../../shared/empty.util' ;
3- import { Operation } from 'fast-json-patch' ;
3+ import { MoveOperation , Operation } from 'fast-json-patch' ;
44import { MetadataPatchReplaceOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-replace-operation.model' ;
55import { MetadataPatchRemoveOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-remove-operation.model' ;
66import { MetadataPatchAddOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-add-operation.model' ;
7- import { MetadataPatchOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-operation.model' ;
7+ import { ArrayMoveChangeAnalyzer } from '../../core/data/array-move-change-analyzer.service' ;
8+ import { MetadataPatchMoveOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-move-operation.model' ;
89
910/* tslint:disable:max-classes-per-file */
1011
@@ -69,7 +70,7 @@ export class DsoEditMetadataValue {
6970 */
7071 confirmChanges ( finishEditing = false ) {
7172 if ( hasNoValue ( this . change ) || this . change === DsoEditMetadataChangeType . UPDATE ) {
72- if ( ( this . originalValue . value !== this . newValue . value || this . originalValue . language !== this . newValue . language ) ) {
73+ if ( ( this . originalValue . value !== this . newValue . value || this . originalValue . language !== this . newValue . language || this . originalValue . place !== this . newValue . place ) ) {
7374 this . change = DsoEditMetadataChangeType . UPDATE ;
7475 } else {
7576 this . change = undefined ;
@@ -187,7 +188,7 @@ export class DsoEditMetadataForm {
187188 Object . entries ( metadata ) . forEach ( ( [ mdField , values ] : [ string , MetadataValue [ ] ] ) => {
188189 this . originalFieldKeys . push ( mdField ) ;
189190 this . fieldKeys . push ( mdField ) ;
190- this . fields [ mdField ] = values . map ( ( value : MetadataValue ) => new DsoEditMetadataValue ( value ) ) ;
191+ this . setValuesForFieldSorted ( mdField , values . map ( ( value : MetadataValue ) => new DsoEditMetadataValue ( value ) ) ) ;
191192 } ) ;
192193 }
193194
@@ -208,6 +209,10 @@ export class DsoEditMetadataForm {
208209 setMetadataField ( mdField : string ) : void {
209210 this . newValue . editing = false ;
210211 this . addValueToField ( this . newValue , mdField ) ;
212+ // Set the place property to match the new value's position within its field
213+ const place = this . fields [ mdField ] . length - 1 ;
214+ this . fields [ mdField ] [ place ] . originalValue . place = place ;
215+ this . fields [ mdField ] [ place ] . newValue . place = place ;
211216 this . newValue = undefined ;
212217 }
213218
@@ -253,6 +258,7 @@ export class DsoEditMetadataForm {
253258 */
254259 discard ( ) : void {
255260 this . resetReinstatable ( ) ;
261+ // Discard changes from each value from each field
256262 Object . entries ( this . fields ) . forEach ( ( [ field , values ] : [ string , DsoEditMetadataValue [ ] ] ) => {
257263 let removeFromIndex = - 1 ;
258264 values . forEach ( ( value : DsoEditMetadataValue , index : number ) => {
@@ -272,25 +278,39 @@ export class DsoEditMetadataForm {
272278 this . fields [ field ] . splice ( removeFromIndex , this . fields [ field ] . length - removeFromIndex ) ;
273279 }
274280 } ) ;
281+ // Delete new metadata fields
275282 this . fieldKeys . forEach ( ( field : string ) => {
276283 if ( this . originalFieldKeys . indexOf ( field ) < 0 ) {
277284 delete this . fields [ field ] ;
278285 }
279286 } ) ;
280287 this . fieldKeys = [ ...this . originalFieldKeys ] ;
288+ // Reset the order of values within their fields to match their place property
289+ this . fieldKeys . forEach ( ( field : string ) => {
290+ this . setValuesForFieldSorted ( field , this . fields [ field ] ) ;
291+ } ) ;
281292 }
282293
294+ /**
295+ * Undo any previously discarded changes
296+ */
283297 reinstate ( ) : void {
298+ // Reinstate each value
284299 Object . values ( this . fields ) . forEach ( ( values : DsoEditMetadataValue [ ] ) => {
285300 values . forEach ( ( value : DsoEditMetadataValue ) => {
286301 value . reinstate ( ) ;
287302 } ) ;
288303 } ) ;
304+ // Re-add new values
289305 Object . entries ( this . reinstatableNewValues ) . forEach ( ( [ field , values ] : [ string , DsoEditMetadataValue [ ] ] ) => {
290306 values . forEach ( ( value : DsoEditMetadataValue ) => {
291307 this . addValueToField ( value , field ) ;
292308 } ) ;
293309 } ) ;
310+ // Reset the order of values within their fields to match their place property
311+ this . fieldKeys . forEach ( ( field : string ) => {
312+ this . setValuesForFieldSorted ( field , this . fields [ field ] ) ;
313+ } ) ;
294314 this . reinstatableNewValues = { } ;
295315 }
296316
@@ -316,35 +336,74 @@ export class DsoEditMetadataForm {
316336 } ) ;
317337 }
318338
339+ /**
340+ * Set the values of a metadata field and sort them by their newValue's place property
341+ * @param mdField
342+ * @param values
343+ */
344+ private setValuesForFieldSorted ( mdField : string , values : DsoEditMetadataValue [ ] ) {
345+ this . fields [ mdField ] = values . sort ( ( a : DsoEditMetadataValue , b : DsoEditMetadataValue ) => a . newValue . place - b . newValue . place ) ;
346+ }
347+
319348 /**
320349 * Get the json PATCH operations for the current changes within this form
350+ * For each metadata field, it'll return operations in the following order: replace, remove (from last to first place), add and move
351+ * This order is important, as each operation is executed in succession of the previous one
321352 */
322- getOperations ( ) : Operation [ ] {
353+ getOperations ( moveAnalyser : ArrayMoveChangeAnalyzer < number > ) : Operation [ ] {
323354 const operations : Operation [ ] = [ ] ;
324355 Object . entries ( this . fields ) . forEach ( ( [ field , values ] : [ string , DsoEditMetadataValue [ ] ] ) => {
325- values . forEach ( ( value : DsoEditMetadataValue , place : number ) => {
326- if ( value . hasChanges ( ) ) {
327- let operation : MetadataPatchOperation ;
328- if ( value . change === DsoEditMetadataChangeType . UPDATE ) {
329- operation = new MetadataPatchReplaceOperation ( field , place , {
330- value : value . newValue . value ,
331- language : value . newValue . language ,
332- } ) ;
333- } else if ( value . change === DsoEditMetadataChangeType . REMOVE ) {
334- operation = new MetadataPatchRemoveOperation ( field , place ) ;
335- } else if ( value . change === DsoEditMetadataChangeType . ADD ) {
336- operation = new MetadataPatchAddOperation ( field , {
337- value : value . newValue . value ,
338- language : value . newValue . language ,
339- } ) ;
340- } else {
341- console . warn ( 'Illegal metadata change state detected for' , value ) ;
342- }
343- if ( hasValue ( operation ) ) {
344- operations . push ( operation . toOperation ( ) ) ;
356+ const replaceOperations : MetadataPatchReplaceOperation [ ] = [ ] ;
357+ const removeOperations : MetadataPatchRemoveOperation [ ] = [ ] ;
358+ const addOperations : MetadataPatchAddOperation [ ] = [ ] ;
359+ values
360+ . sort ( ( a : DsoEditMetadataValue , b : DsoEditMetadataValue ) => a . originalValue . place - b . originalValue . place )
361+ . forEach ( ( value : DsoEditMetadataValue ) => {
362+ if ( value . hasChanges ( ) ) {
363+ if ( value . change === DsoEditMetadataChangeType . UPDATE ) {
364+ // Only changes to value or language are considered "replace" operations. Changes to place are considered "move", which is processed below.
365+ if ( value . originalValue . value !== value . newValue . value || value . originalValue . language !== value . newValue . language ) {
366+ replaceOperations . push ( new MetadataPatchReplaceOperation ( field , value . originalValue . place , {
367+ value : value . newValue . value ,
368+ language : value . newValue . language ,
369+ } ) ) ;
370+ }
371+ } else if ( value . change === DsoEditMetadataChangeType . REMOVE ) {
372+ removeOperations . push ( new MetadataPatchRemoveOperation ( field , value . originalValue . place ) ) ;
373+ } else if ( value . change === DsoEditMetadataChangeType . ADD ) {
374+ addOperations . push ( new MetadataPatchAddOperation ( field , {
375+ value : value . newValue . value ,
376+ language : value . newValue . language ,
377+ } ) ) ;
378+ } else {
379+ console . warn ( 'Illegal metadata change state detected for' , value ) ;
380+ }
345381 }
346- }
347- } ) ;
382+ } ) ;
383+
384+ operations . push ( ...replaceOperations
385+ . map ( ( operation : MetadataPatchReplaceOperation ) => operation . toOperation ( ) ) ) ;
386+ operations . push ( ...removeOperations
387+ // Sort remove operations backwards first, because they get executed in order. This avoids one removal affecting the next.
388+ . sort ( ( a : MetadataPatchRemoveOperation , b : MetadataPatchRemoveOperation ) => b . place - a . place )
389+ . map ( ( operation : MetadataPatchRemoveOperation ) => operation . toOperation ( ) ) ) ;
390+ operations . push ( ...addOperations
391+ . map ( ( operation : MetadataPatchAddOperation ) => operation . toOperation ( ) ) ) ;
392+ } ) ;
393+ // Calculate and add the move operations that need to happen in order to move value from their old place to their new within the field
394+ // This uses an ArrayMoveChangeAnalyzer
395+ Object . entries ( this . fields ) . forEach ( ( [ field , values ] : [ string , DsoEditMetadataValue [ ] ] ) => {
396+ // Exclude values marked for removal, because operations are executed in order (remove first, then move)
397+ const valuesWithoutRemoved = values . filter ( ( value : DsoEditMetadataValue ) => value . change !== DsoEditMetadataChangeType . REMOVE ) ;
398+ const moveOperations = moveAnalyser
399+ . diff (
400+ valuesWithoutRemoved
401+ . map ( ( value : DsoEditMetadataValue ) => value . originalValue . place ) ,
402+ valuesWithoutRemoved
403+ . sort ( ( a : DsoEditMetadataValue , b : DsoEditMetadataValue ) => a . newValue . place - b . newValue . place )
404+ . map ( ( value : DsoEditMetadataValue ) => value . originalValue . place ) )
405+ . map ( ( operation : MoveOperation ) => new MetadataPatchMoveOperation ( field , + operation . from . substr ( 1 ) , + operation . path . substr ( 1 ) ) . toOperation ( ) ) ;
406+ operations . push ( ...moveOperations ) ;
348407 } ) ;
349408 return operations ;
350409 }
0 commit comments