diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html
index dc017a9f92..82ca1f58d9 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html
@@ -36,7 +36,8 @@
+ [columnSizes]="columnSizes"
+ (dropObject)="dropBitstream(bundle, $event)">
observableZip(...bundles.map((bundle: Bundle) =>
- this.objectUpdatesService.getMoveOperations(bundle.self).pipe(
- take(1),
- map((operations: MoveOperation[]) => [...operations.map((operation: MoveOperation) => Object.assign(operation, {
- from: `/_links/bitstreams${operation.from}/href`,
- path: `/_links/bitstreams${operation.path}/href`
- }))])
- )
- )))
- );
-
- // Send out an immediate patch request for each bundle
- const patchResponses$ = observableCombineLatest(bundlesOnce$, moveOperations$).pipe(
- switchMap(([bundles, moveOperationList]: [Bundle[], Operation[][]]) =>
- observableZip(...bundles.map((bundle: Bundle, index: number) => {
- if (isNotEmpty(moveOperationList[index])) {
- return this.bundleService.patch(bundle, moveOperationList[index]);
- } else {
- return observableOf(undefined);
- }
- }))
- )
- );
-
// Fetch all removed bitstreams from the object update service
const removedBitstreams$ = bundlesOnce$.pipe(
switchMap((bundles: Bundle[]) => observableZip(
@@ -201,19 +172,35 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
);
// Perform the setup actions from above in order and display notifications
- patchResponses$.pipe(
- switchMap((responses: RestResponse[]) => {
- this.displayNotifications('item.edit.bitstreams.notifications.move', responses);
- return removedResponses$
- }),
- take(1)
- ).subscribe((responses: RestResponse[]) => {
+ removedResponses$.pipe(take(1)).subscribe((responses: RestResponse[]) => {
this.displayNotifications('item.edit.bitstreams.notifications.remove', responses);
this.reset();
this.submitting = false;
});
}
+ /**
+ * A bitstream was dropped in a new location. Send out a Move Patch request to the REST API, display notifications,
+ * refresh the bundle's cache (so the lists can properly reload) and call the event's callback function (which will
+ * navigate the user to the correct page)
+ * @param bundle The bundle to send patch requests to
+ * @param event The event containing the index the bitstream came from and was dropped to
+ */
+ dropBitstream(bundle: Bundle, event: any) {
+ if (hasValue(event) && hasValue(event.fromIndex) && hasValue(event.toIndex) && hasValue(event.finish)) {
+ const moveOperation = Object.assign({
+ op: 'move',
+ from: `/_links/bitstreams/${event.fromIndex}/href`,
+ path: `/_links/bitstreams/${event.toIndex}/href`
+ });
+ this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RestResponse) => {
+ this.displayNotifications('item.edit.bitstreams.notifications.move', [response]);
+ this.requestService.removeByHrefSubstring(bundle.self);
+ event.finish();
+ });
+ }
+ }
+
/**
* Display notifications
* - Error notification for each failed response with their message
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html
index 58273bb931..c28ef9b525 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html
@@ -17,5 +17,5 @@
-
+
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts
index 115e326241..72e2055bf7 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
+import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core';
import { Bundle } from '../../../../core/shared/bundle.model';
import { Item } from '../../../../core/shared/item.model';
import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes';
@@ -36,6 +36,13 @@ export class ItemEditBitstreamBundleComponent implements OnInit {
*/
@Input() columnSizes: ResponsiveTableSizes;
+ /**
+ * Send an event when the user drops an object on the pagination
+ * The event contains details about the index the object came from and is dropped to (across the entirety of the list,
+ * not just within a single page)
+ */
+ @Output() dropObject: EventEmitter = new EventEmitter();
+
/**
* The bootstrap sizes used for the Bundle Name column
* This column stretches over the first 3 columns and thus is a combination of their sizes processed in ngOnInit
diff --git a/src/app/core/data/object-updates/object-updates.actions.ts b/src/app/core/data/object-updates/object-updates.actions.ts
index 94918157ee..f26be768b1 100644
--- a/src/app/core/data/object-updates/object-updates.actions.ts
+++ b/src/app/core/data/object-updates/object-updates.actions.ts
@@ -8,7 +8,6 @@ import {INotification} from '../../../shared/notifications/models/notification.m
*/
export const ObjectUpdatesActionTypes = {
INITIALIZE_FIELDS: type('dspace/core/cache/object-updates/INITIALIZE_FIELDS'),
- ADD_PAGE_TO_CUSTOM_ORDER: type('dspace/core/cache/object-updates/ADD_PAGE_TO_CUSTOM_ORDER'),
SET_EDITABLE_FIELD: type('dspace/core/cache/object-updates/SET_EDITABLE_FIELD'),
SET_VALID_FIELD: type('dspace/core/cache/object-updates/SET_VALID_FIELD'),
ADD_FIELD: type('dspace/core/cache/object-updates/ADD_FIELD'),
@@ -17,8 +16,7 @@ export const ObjectUpdatesActionTypes = {
REINSTATE: type('dspace/core/cache/object-updates/REINSTATE'),
REMOVE: type('dspace/core/cache/object-updates/REMOVE'),
REMOVE_ALL: type('dspace/core/cache/object-updates/REMOVE_ALL'),
- REMOVE_FIELD: type('dspace/core/cache/object-updates/REMOVE_FIELD'),
- MOVE: type('dspace/core/cache/object-updates/MOVE'),
+ REMOVE_FIELD: type('dspace/core/cache/object-updates/REMOVE_FIELD')
};
/* tslint:disable:max-classes-per-file */
@@ -29,8 +27,7 @@ export const ObjectUpdatesActionTypes = {
export enum FieldChangeType {
UPDATE = 0,
ADD = 1,
- REMOVE = 2,
- MOVE = 3
+ REMOVE = 2
}
/**
@@ -41,10 +38,7 @@ export class InitializeFieldsAction implements Action {
payload: {
url: string,
fields: Identifiable[],
- lastModified: Date,
- order: string[],
- pageSize: number,
- page: number
+ lastModified: Date
};
/**
@@ -61,42 +55,9 @@ export class InitializeFieldsAction implements Action {
constructor(
url: string,
fields: Identifiable[],
- lastModified: Date,
- order: string[] = [],
- pageSize: number = 9999,
- page: number = 0
+ lastModified: Date
) {
- this.payload = { url, fields, lastModified, order, pageSize, page };
- }
-}
-
-/**
- * An ngrx action to initialize a new page's fields in the ObjectUpdates state
- */
-export class AddPageToCustomOrderAction implements Action {
- type = ObjectUpdatesActionTypes.ADD_PAGE_TO_CUSTOM_ORDER;
- payload: {
- url: string,
- fields: Identifiable[],
- order: string[],
- page: number
- };
-
- /**
- * Create a new AddPageToCustomOrderAction
- *
- * @param url The unique url of the page for which the fields are being added
- * @param fields The identifiable fields of which the updates are kept track of
- * @param order A custom order to keep track of objects moving around
- * @param page The page to populate in the custom order
- */
- constructor(
- url: string,
- fields: Identifiable[],
- order: string[] = [],
- page: number = 0
- ) {
- this.payload = { url, fields, order, page };
+ this.payload = { url, fields, lastModified };
}
}
@@ -320,43 +281,6 @@ export class RemoveFieldUpdateAction implements Action {
}
}
-/**
- * An ngrx action to remove a single field update in the ObjectUpdates state for a certain page url and field uuid
- */
-export class MoveFieldUpdateAction implements Action {
- type = ObjectUpdatesActionTypes.MOVE;
- payload: {
- url: string,
- from: number,
- to: number,
- fromPage: number,
- toPage: number,
- field?: Identifiable
- };
-
- /**
- * Create a new RemoveObjectUpdatesAction
- *
- * @param url
- * the unique url of the page for which a field's change should be removed
- * @param from The index of the object to move
- * @param to The index to move the object to
- * @param fromPage The page to move the object from
- * @param toPage The page to move the object to
- * @param field Optional field to add to the fieldUpdates list (useful when we want to track updates across multiple pages)
- */
- constructor(
- url: string,
- from: number,
- to: number,
- fromPage: number,
- toPage: number,
- field?: Identifiable
- ) {
- this.payload = { url, from, to, fromPage, toPage, field };
- }
-}
-
/* tslint:enable:max-classes-per-file */
/**
@@ -369,8 +293,6 @@ export type ObjectUpdatesAction
| ReinstateObjectUpdatesAction
| RemoveObjectUpdatesAction
| RemoveFieldUpdateAction
- | MoveFieldUpdateAction
- | AddPageToCustomOrderAction
| RemoveAllObjectUpdatesAction
| SelectVirtualMetadataAction
| SetEditableFieldUpdateAction
diff --git a/src/app/core/data/object-updates/object-updates.reducer.ts b/src/app/core/data/object-updates/object-updates.reducer.ts
index 759a9f5c87..b1626a5ff5 100644
--- a/src/app/core/data/object-updates/object-updates.reducer.ts
+++ b/src/app/core/data/object-updates/object-updates.reducer.ts
@@ -1,8 +1,8 @@
import {
- AddFieldUpdateAction, AddPageToCustomOrderAction,
+ AddFieldUpdateAction,
DiscardObjectUpdatesAction,
FieldChangeType,
- InitializeFieldsAction, MoveFieldUpdateAction,
+ InitializeFieldsAction,
ObjectUpdatesAction,
ObjectUpdatesActionTypes,
ReinstateObjectUpdatesAction,
@@ -12,9 +12,7 @@ import {
SetValidFieldUpdateAction,
SelectVirtualMetadataAction,
} from './object-updates.actions';
-import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
-import { moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
-import { from } from 'rxjs/internal/observable/from';
+import { hasNoValue, hasValue } from '../../../shared/empty.util';
import {Relationship} from '../../shared/item-relationships/relationship.model';
/**
@@ -83,20 +81,6 @@ export interface DeleteRelationship extends Relationship {
keepRightVirtualMetadata: boolean,
}
-/**
- * A custom order given to the list of objects
- */
-export interface CustomOrder {
- initialOrderPages: OrderPage[],
- newOrderPages: OrderPage[],
- pageSize: number;
- changed: boolean
-}
-
-export interface OrderPage {
- order: string[]
-}
-
/**
* The updated state of a single page
*/
@@ -105,7 +89,6 @@ export interface ObjectUpdatesEntry {
fieldUpdates: FieldUpdates;
virtualMetadataSources: VirtualMetadataSources;
lastModified: Date;
- customOrder: CustomOrder
}
/**
@@ -138,9 +121,6 @@ export function objectUpdatesReducer(state = initialState, action: ObjectUpdates
case ObjectUpdatesActionTypes.INITIALIZE_FIELDS: {
return initializeFieldsUpdate(state, action as InitializeFieldsAction);
}
- case ObjectUpdatesActionTypes.ADD_PAGE_TO_CUSTOM_ORDER: {
- return addPageToCustomOrder(state, action as AddPageToCustomOrderAction);
- }
case ObjectUpdatesActionTypes.ADD_FIELD: {
return addFieldUpdate(state, action as AddFieldUpdateAction);
}
@@ -168,9 +148,6 @@ export function objectUpdatesReducer(state = initialState, action: ObjectUpdates
case ObjectUpdatesActionTypes.SET_VALID_FIELD: {
return setValidFieldUpdate(state, action as SetValidFieldUpdateAction);
}
- case ObjectUpdatesActionTypes.MOVE: {
- return moveFieldUpdate(state, action as MoveFieldUpdateAction);
- }
default: {
return state;
}
@@ -186,50 +163,18 @@ function initializeFieldsUpdate(state: any, action: InitializeFieldsAction) {
const url: string = action.payload.url;
const fields: Identifiable[] = action.payload.fields;
const lastModifiedServer: Date = action.payload.lastModified;
- const order = action.payload.order;
- const pageSize = action.payload.pageSize;
- const page = action.payload.page;
const fieldStates = createInitialFieldStates(fields);
- const initialOrderPages = addOrderToPages([], order, pageSize, page);
const newPageState = Object.assign(
{},
state[url],
{ fieldStates: fieldStates },
{ fieldUpdates: {} },
{ virtualMetadataSources: {} },
- { lastModified: lastModifiedServer },
- { customOrder: {
- initialOrderPages: initialOrderPages,
- newOrderPages: initialOrderPages,
- pageSize: pageSize,
- changed: false }
- }
+ { lastModified: lastModifiedServer }
);
return Object.assign({}, state, { [url]: newPageState });
}
-/**
- * Add a page of objects to the state of a specific url and update a specific page of the custom order
- * @param state The current state
- * @param action The action to perform on the current state
- */
-function addPageToCustomOrder(state: any, action: AddPageToCustomOrderAction) {
- const url: string = action.payload.url;
- const fields: Identifiable[] = action.payload.fields;
- const fieldStates = createInitialFieldStates(fields);
- const order = action.payload.order;
- const page = action.payload.page;
- const pageState: ObjectUpdatesEntry = state[url] || {};
- const newPageState = Object.assign({}, pageState, {
- fieldStates: Object.assign({}, pageState.fieldStates, fieldStates),
- customOrder: Object.assign({}, pageState.customOrder, {
- newOrderPages: addOrderToPages(pageState.customOrder.newOrderPages, order, pageState.customOrder.pageSize, page),
- initialOrderPages: addOrderToPages(pageState.customOrder.initialOrderPages, order, pageState.customOrder.pageSize, page)
- })
- });
- return Object.assign({}, state, { [url]: newPageState });
-}
-
/**
* Add a new update for a specific field to the store
* @param state The current state
@@ -338,19 +283,9 @@ function discardObjectUpdatesFor(url: string, state: any) {
}
});
- const newCustomOrder = Object.assign({}, pageState.customOrder);
- if (pageState.customOrder.changed) {
- const initialOrder = pageState.customOrder.initialOrderPages;
- if (isNotEmpty(initialOrder)) {
- newCustomOrder.newOrderPages = initialOrder;
- newCustomOrder.changed = false;
- }
- }
-
const discardedPageState = Object.assign({}, pageState, {
fieldUpdates: {},
- fieldStates: newFieldStates,
- customOrder: newCustomOrder
+ fieldStates: newFieldStates
});
return Object.assign({}, state, { [url]: discardedPageState }, { [url + OBJECT_UPDATES_TRASH_PATH]: pageState });
}
@@ -504,121 +439,3 @@ function createInitialFieldStates(fields: Identifiable[]) {
uuids.forEach((uuid: string) => fieldStates[uuid] = initialFieldState);
return fieldStates;
}
-
-/**
- * Method to add a list of objects to an existing FieldStates object
- * @param fieldStates FieldStates to add states to
- * @param fields Identifiable objects The list of objects to add to the FieldStates
- */
-function addFieldStates(fieldStates: FieldStates, fields: Identifiable[]) {
- const uuids = fields.map((field: Identifiable) => field.uuid);
- uuids.forEach((uuid: string) => fieldStates[uuid] = initialFieldState);
- return fieldStates;
-}
-
-/**
- * Move an object within the custom order of a page state
- * @param state The current state
- * @param action The move action to perform
- */
-function moveFieldUpdate(state: any, action: MoveFieldUpdateAction) {
- const url = action.payload.url;
- const fromIndex = action.payload.from;
- const toIndex = action.payload.to;
- const fromPage = action.payload.fromPage;
- const toPage = action.payload.toPage;
- const field = action.payload.field;
-
- const pageState: ObjectUpdatesEntry = state[url];
- const initialOrderPages = pageState.customOrder.initialOrderPages;
- const customOrderPages = [...pageState.customOrder.newOrderPages];
-
- // Create a copy of the custom orders for the from- and to-pages
- const fromPageOrder = [...customOrderPages[fromPage].order];
- const toPageOrder = [...customOrderPages[toPage].order];
- if (fromPage === toPage) {
- if (isNotEmpty(customOrderPages[fromPage]) && isNotEmpty(customOrderPages[fromPage].order[fromIndex]) && isNotEmpty(customOrderPages[fromPage].order[toIndex])) {
- // Move an item from one index to another within the same page
- moveItemInArray(fromPageOrder, fromIndex, toIndex);
- // Update the custom order for this page
- customOrderPages[fromPage] = { order: fromPageOrder };
- }
- } else {
- if (isNotEmpty(customOrderPages[fromPage]) && hasValue(customOrderPages[toPage]) && isNotEmpty(customOrderPages[fromPage].order[fromIndex])) {
- // Move an item from one index of one page to an index in another page
- transferArrayItem(fromPageOrder, toPageOrder, fromIndex, toIndex);
- // Update the custom order for both pages
- customOrderPages[fromPage] = { order: fromPageOrder };
- customOrderPages[toPage] = { order: toPageOrder };
- }
- }
-
- // Create a field update if it doesn't exist for this field yet
- let fieldUpdate = {};
- if (hasValue(field)) {
- fieldUpdate = pageState.fieldUpdates[field.uuid];
- if (hasNoValue(fieldUpdate)) {
- fieldUpdate = { field: field, changeType: undefined }
- }
- }
-
- // Update the store's state with new values and return
- return Object.assign({}, state, { [url]: Object.assign({}, pageState, {
- fieldUpdates: Object.assign({}, pageState.fieldUpdates, hasValue(field) ? { [field.uuid]: fieldUpdate } : {}),
- customOrder: Object.assign({}, pageState.customOrder, { newOrderPages: customOrderPages, changed: checkForOrderChanges(initialOrderPages, customOrderPages) })
- })})
-}
-
-/**
- * Compare two lists of OrderPage objects and return whether there's at least one change in the order of objects within
- * @param initialOrderPages The initial list of OrderPages
- * @param customOrderPages The changed list of OrderPages
- */
-function checkForOrderChanges(initialOrderPages: OrderPage[], customOrderPages: OrderPage[]) {
- let changed = false;
- initialOrderPages.forEach((orderPage: OrderPage, page: number) => {
- if (isNotEmpty(orderPage) && isNotEmpty(orderPage.order) && isNotEmpty(customOrderPages[page]) && isNotEmpty(customOrderPages[page].order)) {
- orderPage.order.forEach((id: string, index: number) => {
- if (id !== customOrderPages[page].order[index]) {
- changed = true;
- return;
- }
- });
- if (changed) {
- return;
- }
- }
- });
- return changed;
-}
-
-/**
- * Initialize a custom order page by providing the list of all pages, a list of UUIDs, pageSize and the page to populate
- * @param initialPages The initial list of OrderPage objects
- * @param order The list of UUIDs to create a page for
- * @param pageSize The pageSize used to populate empty spacer pages
- * @param page The index of the page to add
- */
-function addOrderToPages(initialPages: OrderPage[], order: string[], pageSize: number, page: number): OrderPage[] {
- const result = [...initialPages];
- const orderPage: OrderPage = { order: order };
- if (page < result.length) {
- // The page we're trying to add already exists in the list. Overwrite it.
- result[page] = orderPage;
- } else if (page === result.length) {
- // The page we're trying to add is the next page in the list, add it.
- result.push(orderPage);
- } else {
- // The page we're trying to add is at least one page ahead of the list, fill the list with empty pages before adding the page.
- const emptyOrder = [];
- for (let i = 0; i < pageSize; i++) {
- emptyOrder.push(undefined);
- }
- const emptyOrderPage: OrderPage = { order: emptyOrder };
- for (let i = result.length; i < page; i++) {
- result.push(emptyOrderPage);
- }
- result.push(orderPage);
- }
- 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 c9a7f47e81..779a22fb5b 100644
--- a/src/app/core/data/object-updates/object-updates.service.ts
+++ b/src/app/core/data/object-updates/object-updates.service.ts
@@ -8,16 +8,15 @@ import {
Identifiable,
OBJECT_UPDATES_TRASH_PATH,
ObjectUpdatesEntry,
- ObjectUpdatesState, OrderPage,
+ ObjectUpdatesState,
VirtualMetadataSource
} from './object-updates.reducer';
import { Observable } from 'rxjs';
import {
- AddFieldUpdateAction, AddPageToCustomOrderAction,
+ AddFieldUpdateAction,
DiscardObjectUpdatesAction,
FieldChangeType,
InitializeFieldsAction,
- MoveFieldUpdateAction,
ReinstateObjectUpdatesAction,
RemoveFieldUpdateAction,
SelectVirtualMetadataAction,
@@ -27,9 +26,6 @@ import {
import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
import { INotification } from '../../../shared/notifications/models/notification.model';
-import { ArrayMoveChangeAnalyzer } from '../array-move-change-analyzer.service';
-import { MoveOperation } from 'fast-json-patch/lib/core';
-import { flatten } from '@angular/compiler';
function objectUpdatesStateSelector(): MemoizedSelector {
return createSelector(coreSelector, (state: CoreState) => state['cache/object-updates']);
@@ -52,9 +48,7 @@ function virtualMetadataSourceSelector(url: string, source: string): MemoizedSel
*/
@Injectable()
export class ObjectUpdatesService {
- constructor(private store: Store,
- private comparator: ArrayMoveChangeAnalyzer) {
-
+ constructor(private store: Store) {
}
/**
@@ -67,28 +61,6 @@ export class ObjectUpdatesService {
this.store.dispatch(new InitializeFieldsAction(url, fields, lastModified));
}
- /**
- * Method to dispatch an InitializeFieldsAction to the store and keeping track of the order objects are stored
- * @param url The page's URL for which the changes are being mapped
- * @param fields The initial fields for the page's object
- * @param lastModified The date the object was last modified
- * @param pageSize The page size to use for adding pages to the custom order
- * @param page The first page to populate the custom order with
- */
- initializeWithCustomOrder(url, fields: Identifiable[], lastModified: Date, pageSize = 9999, page = 0): void {
- this.store.dispatch(new InitializeFieldsAction(url, fields, lastModified, fields.map((field) => field.uuid), pageSize, page));
- }
-
- /**
- * Method to dispatch an AddPageToCustomOrderAction, adding a new page to an already existing custom order tracking
- * @param url The URL for which the changes are being mapped
- * @param fields The fields to add a new page for
- * @param page The page number (starting from index 0)
- */
- addPageToCustomOrder(url, fields: Identifiable[], page: number): void {
- this.store.dispatch(new AddPageToCustomOrderAction(url, fields, fields.map((field) => field.uuid), page));
- }
-
/**
* Method to dispatch an AddFieldUpdateAction to the store
* @param url The page's URL for which the changes are saved
@@ -166,31 +138,6 @@ export class ObjectUpdatesService {
}))
}
- /**
- * Method that combines the state's updates with the initial values (when there's no update),
- * sorted by their custom order to create a FieldUpdates object
- * @param url The URL of the page for which the FieldUpdates should be requested
- * @param initialFields The initial values of the fields
- * @param page The page to retrieve
- */
- getFieldUpdatesByCustomOrder(url: string, initialFields: Identifiable[], page = 0): Observable {
- const objectUpdates = this.getObjectEntry(url);
- return objectUpdates.pipe(map((objectEntry) => {
- const fieldUpdates: FieldUpdates = {};
- if (hasValue(objectEntry) && hasValue(objectEntry.customOrder) && isNotEmpty(objectEntry.customOrder.newOrderPages) && page < objectEntry.customOrder.newOrderPages.length) {
- for (const uuid of objectEntry.customOrder.newOrderPages[page].order) {
- let fieldUpdate = objectEntry.fieldUpdates[uuid];
- if (isEmpty(fieldUpdate)) {
- const identifiable = initialFields.find((object: Identifiable) => object.uuid === uuid);
- fieldUpdate = {field: identifiable, changeType: undefined};
- }
- fieldUpdates[uuid] = fieldUpdate;
- }
- }
- return fieldUpdates;
- }))
- }
-
/**
* Method to check if a specific field is currently editable in the store
* @param url The URL of the page on which the field resides
@@ -260,19 +207,6 @@ export class ObjectUpdatesService {
this.saveFieldUpdate(url, field, FieldChangeType.UPDATE);
}
- /**
- * Dispatches a MoveFieldUpdateAction
- * @param url The page's URL for which the changes are saved
- * @param from The index of the object to move
- * @param to The index to move the object to
- * @param fromPage The page to move the object from
- * @param toPage The page to move the object to
- * @param field Optional field to add to the fieldUpdates list (useful if we want to track updates across multiple pages)
- */
- saveMoveFieldUpdate(url: string, from: number, to: number, fromPage = 0, toPage = 0, field?: Identifiable) {
- this.store.dispatch(new MoveFieldUpdateAction(url, from, to, fromPage, toPage, field));
- }
-
/**
* Check whether the virtual metadata of a given item is selected to be saved as real metadata
* @param url The URL of the page on which the field resides
@@ -387,7 +321,7 @@ export class ObjectUpdatesService {
* @param url The page's url to check for in the store
*/
hasUpdates(url: string): Observable {
- return this.getObjectEntry(url).pipe(map((objectEntry) => hasValue(objectEntry) && (isNotEmpty(objectEntry.fieldUpdates) || objectEntry.customOrder.changed)));
+ return this.getObjectEntry(url).pipe(map((objectEntry) => hasValue(objectEntry) && isNotEmpty(objectEntry.fieldUpdates)));
}
/**
@@ -405,19 +339,4 @@ 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(
- flatten(customOrder.initialOrderPages.map((orderPage: OrderPage) => orderPage.order)),
- flatten(customOrder.newOrderPages.map((orderPage: OrderPage) => orderPage.order)))
- )
- );
- }
-
}
diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts
index a34b5d5bc0..a0f1d3386e 100644
--- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts
+++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts
@@ -5,19 +5,20 @@ import { PaginatedList } from '../../core/data/paginated-list';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
-import { switchMap, take, tap } from 'rxjs/operators';
-import { hasValue, isEmpty, isNotEmpty } from '../empty.util';
+import { switchMap, take } from 'rxjs/operators';
+import { hasValue } from '../empty.util';
import { paginatedListToArray } from '../../core/shared/operators';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
-import { ElementRef, ViewChild } from '@angular/core';
+import { ElementRef, EventEmitter, Output, ViewChild } from '@angular/core';
import { PaginationComponent } from '../pagination/pagination.component';
/**
* An abstract component containing general methods and logic to be able to drag and drop objects within a paginated
* list. This implementation supports being able to drag and drop objects between pages.
- * Dragging an object on top of a page number will automatically detect the page it's being dropped on, send an update
- * to the store and add the object on top of that page.
+ * Dragging an object on top of a page number will automatically detect the page it's being dropped on and send a
+ * dropObject event to the parent component containing detailed information about the indexes the object was dropped from
+ * and to.
*
* To extend this component, it is important to make sure to:
* - Initialize objectsRD$ within the initializeObjectsRD() method
@@ -34,6 +35,13 @@ export abstract class AbstractPaginatedDragAndDropListComponent = new EventEmitter();
+
/**
* The URL to use for accessing the object updates from this list
*/
@@ -52,7 +60,7 @@ export abstract class AbstractPaginatedDragAndDropListComponent(1);
- /**
- * A list of pages that have been initialized in the field-update store
- */
- initializedPages: number[] = [];
-
- /**
- * An object storing information about an update that should be fired whenever fireToUpdate is called
- */
- toUpdate: {
- fromIndex: number,
- toIndex: number,
- fromPage: number,
- toPage: number,
- field?: T
- };
-
protected constructor(protected objectUpdatesService: ObjectUpdatesService,
protected elRef: ElementRef) {
}
@@ -110,28 +102,17 @@ export abstract class AbstractPaginatedDragAndDropListComponent {
+ this.objectUpdatesService.initialize(this.url, objects, new Date());
+ });
this.updates$ = this.objectsRD$.pipe(
paginatedListToArray(),
- tap((objects: T[]) => {
- // Pages in the field-update store are indexed starting at 0 (because they're stored in an array of pages)
- const updatesPage = this.currentPage$.value - 1;
- if (isEmpty(this.initializedPages)) {
- // No updates have been initialized yet for this list, initialize the first page
- this.objectUpdatesService.initializeWithCustomOrder(this.url, objects, new Date(), this.pageSize, updatesPage);
- this.initializedPages.push(updatesPage);
- } else if (this.initializedPages.indexOf(updatesPage) < 0) {
- // Updates were initialized for this list, but not the page we're on. Add the current page to the field-update store for this list
- this.objectUpdatesService.addPageToCustomOrder(this.url, objects, updatesPage);
- this.initializedPages.push(updatesPage);
- }
-
- // The new page is loaded into the store, check if there are any updates waiting and fire those as well
- this.fireToUpdate();
- }),
- switchMap((objects: T[]) => this.objectUpdatesService.getFieldUpdatesByCustomOrder(this.url, objects, this.currentPage$.value - 1))
+ switchMap((objects: T[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, objects))
);
}
@@ -144,52 +125,42 @@ export abstract class AbstractPaginatedDragAndDropListComponent) {
+ const dragIndex = event.previousIndex;
+ let dropIndex = event.currentIndex;
+ const dragPage = this.currentPage$.value - 1;
+ let dropPage = this.currentPage$.value - 1;
+
// Check if the user is hovering over any of the pagination's pages at the time of dropping the object
const droppedOnElement = this.elRef.nativeElement.querySelector('.page-item:hover');
if (hasValue(droppedOnElement) && hasValue(droppedOnElement.textContent)) {
// The user is hovering over a page, fetch the page's number from the element
- const page = Number(droppedOnElement.textContent);
- if (hasValue(page) && !Number.isNaN(page)) {
- const id = event.item.element.nativeElement.id;
- this.updates$.pipe(take(1)).subscribe((updates: FieldUpdates) => {
- const field = hasValue(updates[id]) ? updates[id].field : undefined;
- this.toUpdate = Object.assign({
- fromIndex: event.previousIndex,
- toIndex: 0,
- fromPage: this.currentPage$.value - 1,
- toPage: page - 1,
- field
- });
- // Switch to the dropped-on page and force a page update for the pagination component
- this.currentPage$.next(page);
- this.paginationComponent.doPageChange(page);
- if (this.initializedPages.indexOf(page - 1) >= 0) {
- // The page the object is being dropped to has already been loaded before, directly fire an update to the store.
- // For pages that haven't been loaded before, the updates$ observable will call fireToUpdate after the new page
- // has loaded
- this.fireToUpdate();
- }
- });
+ const droppedPage = Number(droppedOnElement.textContent);
+ if (hasValue(droppedPage) && !Number.isNaN(droppedPage)) {
+ dropPage = droppedPage - 1;
+ dropIndex = 0;
}
- } else {
- this.objectUpdatesService.saveMoveFieldUpdate(this.url, event.previousIndex, event.currentIndex, this.currentPage$.value - 1, this.currentPage$.value - 1);
}
- }
- /**
- * Method checking if there's an update ready to be fired. Send out a MoveFieldUpdate to the store if there's an
- * update present and clear the update afterwards.
- */
- fireToUpdate() {
- if (hasValue(this.toUpdate)) {
- this.objectUpdatesService.saveMoveFieldUpdate(this.url, this.toUpdate.fromIndex, this.toUpdate.toIndex, this.toUpdate.fromPage, this.toUpdate.toPage, this.toUpdate.field);
- this.toUpdate = undefined;
+ const redirectPage = dropPage + 1;
+ const fromIndex = (dragPage * this.pageSize) + dragIndex;
+ const toIndex = (dropPage * this.pageSize) + dropIndex;
+ // Send out a drop event when the field exists and the "from" and "to" indexes are different from each other
+ if (fromIndex !== toIndex) {
+ this.dropObject.emit(Object.assign({
+ fromIndex,
+ toIndex,
+ finish: () => {
+ this.currentPage$.next(redirectPage);
+ this.paginationComponent.doPageChange(redirectPage);
+ }
+ }));
}
}
}