diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts index 2f8a8c738f..ea269137a0 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts @@ -24,6 +24,9 @@ import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-o import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { Bitstream } from '../../../core/shared/bitstream.model'; import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; +import { Operation } from 'fast-json-patch'; +import { MoveOperation } from 'fast-json-patch/lib/core'; +import { BundleDataService } from '../../../core/data/bundle-data.service'; @Component({ selector: 'ds-item-bitstreams', @@ -66,7 +69,8 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme public bitstreamService: BitstreamDataService, public objectCache: ObjectCacheService, public requestService: RequestService, - public cdRef: ChangeDetectorRef + public cdRef: ChangeDetectorRef, + public bundleService: BundleDataService ) { super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route); } @@ -141,6 +145,19 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme this.displayNotifications(responses); this.reset(); }); + + this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => { + bundles.forEach((bundle: Bundle) => { + this.objectUpdatesService.getMoveOperations(bundle.self).pipe( + take(1), + isNotEmptyOperator(), + map((operations: MoveOperation[]) => [...operations.map((operation: MoveOperation) => Object.assign(operation, { + from: `/_links/bitstreams${operation.from}/href`, + path: `/_links/bitstreams${operation.path}/href` + }))]) + ).subscribe((operations: Operation[]) => this.bundleService.patch(bundle.self, operations)); + }); + }); } /** diff --git a/src/app/core/cache/server-sync-buffer.effects.ts b/src/app/core/cache/server-sync-buffer.effects.ts index fd1060880f..dae1ab6f4f 100644 --- a/src/app/core/cache/server-sync-buffer.effects.ts +++ b/src/app/core/cache/server-sync-buffer.effects.ts @@ -104,9 +104,9 @@ export class ServerSyncBufferEffects { map((entry: ObjectCacheEntry) => { if (isNotEmpty(entry.patches)) { const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations)); - const metadataPatch = flatPatch.filter((op: Operation) => op.path.startsWith('/metadata')); - if (isNotEmpty(metadataPatch)) { - this.requestService.configure(new PatchRequest(this.requestService.generateRequestId(), href, metadataPatch)); + const objectPatch = flatPatch.filter((op: Operation) => op.path.startsWith('/metadata') || op.op === 'move'); + if (isNotEmpty(objectPatch)) { + this.requestService.configure(new PatchRequest(this.requestService.generateRequestId(), href, objectPatch)); } } return new ApplyPatchObjectCacheAction(href); diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 0087f26d9f..d1e94f20dd 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -122,6 +122,7 @@ import { BrowseDefinition } from './shared/browse-definition.model'; import { BitstreamDataService } from './data/bitstream-data.service'; import { MappedCollectionsReponseParsingService } from './data/mapped-collections-reponse-parsing.service'; import { ObjectSelectService } from '../shared/object-select/object-select.service'; +import { ArrayMoveChangeAnalyzer } from './data/array-move-change-analyzer.service'; const IMPORTS = [ CommonModule, @@ -201,6 +202,7 @@ const PROVIDERS = [ DSpaceObjectDataService, DSOChangeAnalyzer, DefaultChangeAnalyzer, + ArrayMoveChangeAnalyzer, ObjectSelectService, CSSVariableService, MenuService, diff --git a/src/app/core/data/array-move-change-analyzer.service.ts b/src/app/core/data/array-move-change-analyzer.service.ts new file mode 100644 index 0000000000..cb8f0548d7 --- /dev/null +++ b/src/app/core/data/array-move-change-analyzer.service.ts @@ -0,0 +1,38 @@ +import { MoveOperation, Operation } from 'fast-json-patch/lib/core'; +import { compare } from 'fast-json-patch'; +import { ChangeAnalyzer } from './change-analyzer'; +import { Injectable } from '@angular/core'; +import { CacheableObject } from '../cache/object-cache.reducer'; +import { NormalizedObject } from '../cache/models/normalized-object.model'; +import { moveItemInArray } from '@angular/cdk/drag-drop'; + +/** + * A class to determine move operations between two arrays + */ +@Injectable() +export class ArrayMoveChangeAnalyzer { + + /** + * Compare two arrays detecting and returning move operations + * + * @param array1 The original array + * @param array2 The custom array to compare with the original + */ + diff(array1: T[], array2: T[]): MoveOperation[] { + const result = []; + const moved = [...array1]; + array1.forEach((value: T, index: number) => { + const otherIndex = array2.indexOf(value); + const movedIndex = moved.indexOf(value); + if (index !== otherIndex && movedIndex !== otherIndex) { + moveItemInArray(moved, movedIndex, otherIndex); + result.push(Object.assign({ + op: 'move', + from: '/' + movedIndex, + path: '/' + otherIndex + }) as MoveOperation) + } + }); + return result; + } +} diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts index 9c14748f7f..c50dc465d8 100644 --- a/src/app/core/data/object-updates/object-updates.service.ts +++ b/src/app/core/data/object-updates/object-updates.service.ts @@ -24,6 +24,9 @@ import { import { distinctUntilChanged, filter, map } from 'rxjs/operators'; import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { INotification } from '../../../shared/notifications/models/notification.model'; +import { Operation } from 'fast-json-patch'; +import { ArrayMoveChangeAnalyzer } from '../array-move-change-analyzer.service'; +import { MoveOperation } from 'fast-json-patch/lib/core'; function objectUpdatesStateSelector(): MemoizedSelector { return createSelector(coreSelector, (state: CoreState) => state['cache/object-updates']); @@ -42,7 +45,8 @@ function filterByUrlAndUUIDFieldStateSelector(url: string, uuid: string): Memoiz */ @Injectable() export class ObjectUpdatesService { - constructor(private store: Store) { + constructor(private store: Store, + private comparator: ArrayMoveChangeAnalyzer) { } @@ -335,4 +339,16 @@ export class ObjectUpdatesService { getLastModified(url: string): Observable { return this.getObjectEntry(url).pipe(map((entry: ObjectUpdatesEntry) => entry.lastModified)); } + + /** + * Get move operations based on the custom order + * @param url The page's url + */ + getMoveOperations(url: string): Observable { + return this.getObjectEntry(url).pipe( + map((objectEntry) => objectEntry.customOrder), + map((customOrder) => this.comparator.diff(customOrder.initialOrder, customOrder.newOrder)) + ); + } + }