65717: Ability to discard all field-updates at once (fixes discard and reinstate issues)

This commit is contained in:
Kristof De Langhe
2019-10-21 10:43:46 +02:00
parent 6dca421256
commit b9754764b3
6 changed files with 77 additions and 17 deletions

View File

@@ -157,13 +157,8 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
* Shows a notification to remind the user that they can undo this * Shows a notification to remind the user that they can undo this
*/ */
discard() { discard() {
super.discard();
const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), {timeOut: this.discardTimeOut}); const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), {timeOut: this.discardTimeOut});
this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => { this.objectUpdatesService.discardAllFieldUpdates(this.url, undoNotification);
bundles.forEach((bundle: Bundle) => {
this.objectUpdatesService.discardFieldUpdates(bundle.self, undoNotification);
});
});
} }
/** /**

View File

@@ -26,6 +26,11 @@ export class ItemEditBitstreamBundleComponent implements OnInit {
*/ */
@Input() bundle: Bundle; @Input() bundle: Bundle;
/**
* The current url of this page
*/
@Input() url: string;
/** /**
* The updates to the current bundle * The updates to the current bundle
*/ */

View File

@@ -14,6 +14,7 @@ export const ObjectUpdatesActionTypes = {
DISCARD: type('dspace/core/cache/object-updates/DISCARD'), DISCARD: type('dspace/core/cache/object-updates/DISCARD'),
REINSTATE: type('dspace/core/cache/object-updates/REINSTATE'), REINSTATE: type('dspace/core/cache/object-updates/REINSTATE'),
REMOVE: type('dspace/core/cache/object-updates/REMOVE'), 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'), REMOVE_FIELD: type('dspace/core/cache/object-updates/REMOVE_FIELD'),
}; };
@@ -144,7 +145,8 @@ export class DiscardObjectUpdatesAction implements Action {
type = ObjectUpdatesActionTypes.DISCARD; type = ObjectUpdatesActionTypes.DISCARD;
payload: { payload: {
url: string, url: string,
notification: INotification notification: INotification,
discardAll: boolean;
}; };
/** /**
@@ -153,12 +155,14 @@ export class DiscardObjectUpdatesAction implements Action {
* @param url * @param url
* the unique url of the page for which the changes should be discarded * the unique url of the page for which the changes should be discarded
* @param notification The notification that is raised when changes are discarded * @param notification The notification that is raised when changes are discarded
* @param discardAll discard all
*/ */
constructor( constructor(
url: string, url: string,
notification: INotification notification: INotification,
discardAll = false
) { ) {
this.payload = { url, notification }; this.payload = { url, notification, discardAll };
} }
} }
@@ -206,6 +210,13 @@ export class RemoveObjectUpdatesAction implements Action {
} }
} }
/**
* An ngrx action to remove all previously discarded updates in the ObjectUpdates state
*/
export class RemoveAllObjectUpdatesAction implements Action {
type = ObjectUpdatesActionTypes.REMOVE_ALL;
}
/** /**
* An ngrx action to remove a single field update in the ObjectUpdates state for a certain page url and field uuid * An ngrx action to remove a single field update in the ObjectUpdates state for a certain page url and field uuid
*/ */

View File

@@ -3,12 +3,12 @@ import { Actions, Effect, ofType } from '@ngrx/effects';
import { import {
DiscardObjectUpdatesAction, DiscardObjectUpdatesAction,
ObjectUpdatesAction, ObjectUpdatesAction,
ObjectUpdatesActionTypes, ObjectUpdatesActionTypes, RemoveAllObjectUpdatesAction,
RemoveObjectUpdatesAction RemoveObjectUpdatesAction
} from './object-updates.actions'; } from './object-updates.actions';
import { delay, filter, map, switchMap, take, tap } from 'rxjs/operators'; import { delay, filter, map, switchMap, take, tap } from 'rxjs/operators';
import { of as observableOf, race as observableRace, Subject } from 'rxjs'; import { of as observableOf, race as observableRace, Subject } from 'rxjs';
import { hasNoValue } from '../../../shared/empty.util'; import { hasNoValue, hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { INotification } from '../../../shared/notifications/models/notification.model'; import { INotification } from '../../../shared/notifications/models/notification.model';
import { import {
@@ -16,6 +16,7 @@ import {
NotificationsActionTypes, NotificationsActionTypes,
RemoveNotificationAction RemoveNotificationAction
} from '../../../shared/notifications/notifications.actions'; } from '../../../shared/notifications/notifications.actions';
import { Action } from '@ngrx/store';
/** /**
* NGRX effects for ObjectUpdatesActions * NGRX effects for ObjectUpdatesActions
@@ -53,13 +54,14 @@ export class ObjectUpdatesEffects {
.pipe( .pipe(
ofType(...Object.values(ObjectUpdatesActionTypes)), ofType(...Object.values(ObjectUpdatesActionTypes)),
map((action: ObjectUpdatesAction) => { map((action: ObjectUpdatesAction) => {
if (hasValue(action.payload)) {
const url: string = action.payload.url; const url: string = action.payload.url;
if (hasNoValue(this.actionMap$[url])) { if (hasNoValue(this.actionMap$[url])) {
this.actionMap$[url] = new Subject<ObjectUpdatesAction>(); this.actionMap$[url] = new Subject<ObjectUpdatesAction>();
} }
this.actionMap$[url].next(action); this.actionMap$[url].next(action);
} }
) })
); );
/** /**
@@ -91,9 +93,15 @@ export class ObjectUpdatesEffects {
const url: string = action.payload.url; const url: string = action.payload.url;
const notification: INotification = action.payload.notification; const notification: INotification = action.payload.notification;
const timeOut = notification.options.timeOut; const timeOut = notification.options.timeOut;
let removeAction: Action = new RemoveObjectUpdatesAction(action.payload.url);
if (action.payload.discardAll) {
removeAction = new RemoveAllObjectUpdatesAction();
}
return observableRace( return observableRace(
// Either wait for the delay and perform a remove action // Either wait for the delay and perform a remove action
observableOf(new RemoveObjectUpdatesAction(action.payload.url)).pipe(delay(timeOut)), observableOf(removeAction).pipe(delay(timeOut)),
// Or wait for a a user action // Or wait for a a user action
this.actionMap$[url].pipe( this.actionMap$[url].pipe(
take(1), take(1),
@@ -106,19 +114,19 @@ export class ObjectUpdatesEffects {
return { type: 'NO_ACTION' } return { type: 'NO_ACTION' }
} }
// If someone performed another action, assume the user does not want to reinstate and remove all changes // If someone performed another action, assume the user does not want to reinstate and remove all changes
return new RemoveObjectUpdatesAction(action.payload.url); return removeAction
}) })
), ),
this.notificationActionMap$[notification.id].pipe( this.notificationActionMap$[notification.id].pipe(
filter((notificationsAction: NotificationsActions) => notificationsAction.type === NotificationsActionTypes.REMOVE_NOTIFICATION), filter((notificationsAction: NotificationsActions) => notificationsAction.type === NotificationsActionTypes.REMOVE_NOTIFICATION),
map(() => { map(() => {
return new RemoveObjectUpdatesAction(action.payload.url); return removeAction;
}) })
), ),
this.notificationActionMap$[this.allIdentifier].pipe( this.notificationActionMap$[this.allIdentifier].pipe(
filter((notificationsAction: NotificationsActions) => notificationsAction.type === NotificationsActionTypes.REMOVE_ALL_NOTIFICATIONS), filter((notificationsAction: NotificationsActions) => notificationsAction.type === NotificationsActionTypes.REMOVE_ALL_NOTIFICATIONS),
map(() => { map(() => {
return new RemoveObjectUpdatesAction(action.payload.url); return removeAction;
}) })
) )
) )

View File

@@ -105,6 +105,9 @@ export function objectUpdatesReducer(state = initialState, action: ObjectUpdates
case ObjectUpdatesActionTypes.REMOVE: { case ObjectUpdatesActionTypes.REMOVE: {
return removeObjectUpdates(state, action as RemoveObjectUpdatesAction); return removeObjectUpdates(state, action as RemoveObjectUpdatesAction);
} }
case ObjectUpdatesActionTypes.REMOVE_ALL: {
return removeAllObjectUpdates(state);
}
case ObjectUpdatesActionTypes.REMOVE_FIELD: { case ObjectUpdatesActionTypes.REMOVE_FIELD: {
return removeFieldUpdate(state, action as RemoveFieldUpdateAction); return removeFieldUpdate(state, action as RemoveFieldUpdateAction);
} }
@@ -175,7 +178,24 @@ function addFieldUpdate(state: any, action: AddFieldUpdateAction) {
* @param action The action to perform on the current state * @param action The action to perform on the current state
*/ */
function discardObjectUpdates(state: any, action: DiscardObjectUpdatesAction) { function discardObjectUpdates(state: any, action: DiscardObjectUpdatesAction) {
const url: string = action.payload.url; if (action.payload.discardAll) {
let newState = Object.assign({}, state);
Object.keys(state).filter((path: string) => !path.endsWith(OBJECT_UPDATES_TRASH_PATH)).forEach((path: string) => {
newState = discardObjectUpdatesFor(path, newState);
});
return newState;
} else {
const url: string = action.payload.url;
return discardObjectUpdatesFor(url, state);
}
}
/**
* Discard all updates for a specific action's url in the store
* @param url The action's url
* @param state The current state
*/
function discardObjectUpdatesFor(url: string, state: any) {
const pageState: ObjectUpdatesEntry = state[url]; const pageState: ObjectUpdatesEntry = state[url];
const newFieldStates = {}; const newFieldStates = {};
Object.keys(pageState.fieldStates).forEach((uuid: string) => { Object.keys(pageState.fieldStates).forEach((uuid: string) => {
@@ -228,6 +248,18 @@ function removeObjectUpdatesByURL(state: any, url: string) {
return newState; return newState;
} }
/**
* Remove all updates in the store
* @param state The current state
*/
function removeAllObjectUpdates(state: any) {
const newState = Object.assign({}, state);
Object.keys(state).filter((path: string) => path.endsWith(OBJECT_UPDATES_TRASH_PATH)).forEach((path: string) => {
delete newState[path];
});
return newState;
}
/** /**
* Discard the update for a specific action's url and field UUID in the store * Discard the update for a specific action's url and field UUID in the store
* @param state The current state * @param state The current state

View File

@@ -225,6 +225,15 @@ export class ObjectUpdatesService {
this.store.dispatch(new DiscardObjectUpdatesAction(url, undoNotification)); this.store.dispatch(new DiscardObjectUpdatesAction(url, undoNotification));
} }
/**
* Method to dispatch a DiscardObjectUpdatesAction to the store with discardAll set to true
* @param url The page's URL for which the changes should be discarded
* @param undoNotification The notification which is should possibly be canceled
*/
discardAllFieldUpdates(url: string, undoNotification: INotification) {
this.store.dispatch(new DiscardObjectUpdatesAction(url, undoNotification, true));
}
/** /**
* Method to dispatch an ReinstateObjectUpdatesAction to the store * Method to dispatch an ReinstateObjectUpdatesAction to the store
* @param url The page's URL for which the changes should be reinstated * @param url The page's URL for which the changes should be reinstated