mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 23:13:04 +00:00
410 lines
13 KiB
TypeScript
410 lines
13 KiB
TypeScript
import {
|
|
AddFieldUpdateAction,
|
|
DiscardObjectUpdatesAction,
|
|
FieldChangeType,
|
|
InitializeFieldsAction,
|
|
ObjectUpdatesAction,
|
|
ObjectUpdatesActionTypes,
|
|
ReinstateObjectUpdatesAction,
|
|
RemoveFieldUpdateAction,
|
|
RemoveObjectUpdatesAction,
|
|
SetEditableFieldUpdateAction,
|
|
SetValidFieldUpdateAction,
|
|
SelectVirtualMetadataAction,
|
|
} from './object-updates.actions';
|
|
import { hasNoValue, hasValue } from '../../../shared/empty.util';
|
|
import {Relationship} from '../../shared/item-relationships/relationship.model';
|
|
|
|
/**
|
|
* Path where discarded objects are saved
|
|
*/
|
|
export const OBJECT_UPDATES_TRASH_PATH = '/trash';
|
|
|
|
/**
|
|
* The state for a single field
|
|
*/
|
|
export interface FieldState {
|
|
editable: boolean,
|
|
isNew: boolean,
|
|
isValid: boolean
|
|
}
|
|
|
|
/**
|
|
* A list of states for all the fields for a single page, mapped by uuid
|
|
*/
|
|
export interface FieldStates {
|
|
[uuid: string]: FieldState;
|
|
}
|
|
|
|
/**
|
|
* Represents every object that has a UUID
|
|
*/
|
|
export interface Identifiable {
|
|
uuid: string
|
|
}
|
|
|
|
/**
|
|
* The state of a single field update
|
|
*/
|
|
export interface FieldUpdate {
|
|
field: Identifiable,
|
|
changeType: FieldChangeType
|
|
}
|
|
|
|
/**
|
|
* The states of all field updates available for a single page, mapped by uuid
|
|
*/
|
|
export interface FieldUpdates {
|
|
[uuid: string]: FieldUpdate;
|
|
}
|
|
|
|
/**
|
|
* The states of all virtual metadata selections available for a single page, mapped by the relationship uuid
|
|
*/
|
|
export interface VirtualMetadataSources {
|
|
[source: string]: VirtualMetadataSource
|
|
}
|
|
|
|
/**
|
|
* The selection of virtual metadata for a relationship, mapped by the uuid of either the item or the relationship type
|
|
*/
|
|
export interface VirtualMetadataSource {
|
|
[uuid: string]: boolean,
|
|
}
|
|
|
|
/**
|
|
* A fieldupdate interface which represents a relationship selected to be deleted,
|
|
* along with a selection of the virtual metadata to keep
|
|
*/
|
|
export interface DeleteRelationship extends Relationship {
|
|
keepLeftVirtualMetadata: boolean,
|
|
keepRightVirtualMetadata: boolean,
|
|
}
|
|
|
|
/**
|
|
* The updated state of a single page
|
|
*/
|
|
export interface ObjectUpdatesEntry {
|
|
fieldStates: FieldStates;
|
|
fieldUpdates: FieldUpdates;
|
|
virtualMetadataSources: VirtualMetadataSources;
|
|
lastModified: Date;
|
|
}
|
|
|
|
/**
|
|
* The updated state of all pages, mapped by the page URL
|
|
*/
|
|
export interface ObjectUpdatesState {
|
|
[url: string]: ObjectUpdatesEntry;
|
|
}
|
|
|
|
/**
|
|
* Initial state for an existing initialized field
|
|
*/
|
|
const initialFieldState = { editable: false, isNew: false, isValid: true };
|
|
|
|
/**
|
|
* Initial state for a newly added field
|
|
*/
|
|
const initialNewFieldState = { editable: true, isNew: true, isValid: undefined };
|
|
|
|
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
|
const initialState = Object.create(null);
|
|
|
|
/**
|
|
* Reducer method to calculate the next ObjectUpdates state, based on the current state and the ObjectUpdatesAction
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
export function objectUpdatesReducer(state = initialState, action: ObjectUpdatesAction): ObjectUpdatesState {
|
|
switch (action.type) {
|
|
case ObjectUpdatesActionTypes.INITIALIZE_FIELDS: {
|
|
return initializeFieldsUpdate(state, action as InitializeFieldsAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.ADD_FIELD: {
|
|
return addFieldUpdate(state, action as AddFieldUpdateAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.SELECT_VIRTUAL_METADATA: {
|
|
return selectVirtualMetadata(state, action as SelectVirtualMetadataAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.DISCARD: {
|
|
return discardObjectUpdates(state, action as DiscardObjectUpdatesAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.REINSTATE: {
|
|
return reinstateObjectUpdates(state, action as ReinstateObjectUpdatesAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.REMOVE: {
|
|
return removeObjectUpdates(state, action as RemoveObjectUpdatesAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.REMOVE_FIELD: {
|
|
return removeFieldUpdate(state, action as RemoveFieldUpdateAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.SET_EDITABLE_FIELD: {
|
|
return setEditableFieldUpdate(state, action as SetEditableFieldUpdateAction);
|
|
}
|
|
case ObjectUpdatesActionTypes.SET_VALID_FIELD: {
|
|
return setValidFieldUpdate(state, action as SetValidFieldUpdateAction);
|
|
}
|
|
default: {
|
|
return state;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the state for a specific url and store all its fields in the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
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 fieldStates = createInitialFieldStates(fields);
|
|
const newPageState = Object.assign(
|
|
{},
|
|
state[url],
|
|
{ fieldStates: fieldStates },
|
|
{ fieldUpdates: {} },
|
|
{ virtualMetadataSources: {} },
|
|
{ lastModified: lastModifiedServer }
|
|
);
|
|
return Object.assign({}, state, { [url]: newPageState });
|
|
}
|
|
|
|
/**
|
|
* Add a new update for a specific field to the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function addFieldUpdate(state: any, action: AddFieldUpdateAction) {
|
|
const url: string = action.payload.url;
|
|
const field: Identifiable = action.payload.field;
|
|
const changeType: FieldChangeType = action.payload.changeType;
|
|
const pageState: ObjectUpdatesEntry = state[url] || {};
|
|
|
|
let states = pageState.fieldStates;
|
|
if (changeType === FieldChangeType.ADD) {
|
|
states = Object.assign({}, { [field.uuid]: initialNewFieldState }, pageState.fieldStates)
|
|
}
|
|
|
|
let fieldUpdate: any = pageState.fieldUpdates[field.uuid] || {};
|
|
const newChangeType = determineChangeType(fieldUpdate.changeType, changeType);
|
|
|
|
fieldUpdate = Object.assign({}, { field, changeType: newChangeType });
|
|
|
|
const fieldUpdates = Object.assign({}, pageState.fieldUpdates, { [field.uuid]: fieldUpdate });
|
|
|
|
const newPageState = Object.assign({}, pageState,
|
|
{ fieldStates: states },
|
|
{ fieldUpdates: fieldUpdates });
|
|
return Object.assign({}, state, { [url]: newPageState });
|
|
}
|
|
|
|
/**
|
|
* Update the selected virtual metadata in the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function selectVirtualMetadata(state: any, action: SelectVirtualMetadataAction) {
|
|
|
|
const url: string = action.payload.url;
|
|
const source: string = action.payload.source;
|
|
const uuid: string = action.payload.uuid;
|
|
const select: boolean = action.payload.select;
|
|
|
|
const pageState: ObjectUpdatesEntry = state[url] || {};
|
|
|
|
const virtualMetadataSource = Object.assign(
|
|
{},
|
|
pageState.virtualMetadataSources[source],
|
|
{
|
|
[uuid]: select,
|
|
},
|
|
);
|
|
|
|
const virtualMetadataSources = Object.assign(
|
|
{},
|
|
pageState.virtualMetadataSources,
|
|
{
|
|
[source]: virtualMetadataSource,
|
|
},
|
|
);
|
|
|
|
const newPageState = Object.assign(
|
|
{},
|
|
pageState,
|
|
{virtualMetadataSources: virtualMetadataSources},
|
|
);
|
|
|
|
return Object.assign(
|
|
{},
|
|
state,
|
|
{
|
|
[url]: newPageState,
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Discard all updates for a specific action's url in the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function discardObjectUpdates(state: any, action: DiscardObjectUpdatesAction) {
|
|
const url: string = action.payload.url;
|
|
const pageState: ObjectUpdatesEntry = state[url];
|
|
const newFieldStates = {};
|
|
Object.keys(pageState.fieldStates).forEach((uuid: string) => {
|
|
const fieldState: FieldState = pageState.fieldStates[uuid];
|
|
if (!fieldState.isNew) {
|
|
/* After discarding we don't want the reset fields to stay editable or invalid */
|
|
newFieldStates[uuid] = Object.assign({}, fieldState, { editable: false, isValid: true });
|
|
}
|
|
});
|
|
|
|
const discardedPageState = Object.assign({}, pageState, {
|
|
fieldUpdates: {},
|
|
fieldStates: newFieldStates
|
|
});
|
|
return Object.assign({}, state, { [url]: discardedPageState }, { [url + OBJECT_UPDATES_TRASH_PATH]: pageState });
|
|
}
|
|
|
|
/**
|
|
* Reinstate all updates for a specific action's url in the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function reinstateObjectUpdates(state: any, action: ReinstateObjectUpdatesAction) {
|
|
const url: string = action.payload.url;
|
|
const trashState = state[url + OBJECT_UPDATES_TRASH_PATH];
|
|
|
|
const newState = Object.assign({}, state, { [url]: trashState });
|
|
delete newState[url + OBJECT_UPDATES_TRASH_PATH];
|
|
return newState;
|
|
}
|
|
|
|
/**
|
|
* Remove all updates for a specific action's url in the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function removeObjectUpdates(state: any, action: RemoveObjectUpdatesAction) {
|
|
const url: string = action.payload.url;
|
|
return removeObjectUpdatesByURL(state, url);
|
|
}
|
|
|
|
/**
|
|
* Remove all updates for a specific url in the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function removeObjectUpdatesByURL(state: any, url: string) {
|
|
const newState = Object.assign({}, state);
|
|
delete newState[url + OBJECT_UPDATES_TRASH_PATH];
|
|
return newState;
|
|
}
|
|
|
|
/**
|
|
* Discard the update for a specific action's url and field UUID in the store
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function removeFieldUpdate(state: any, action: RemoveFieldUpdateAction) {
|
|
const url: string = action.payload.url;
|
|
const uuid: string = action.payload.uuid;
|
|
let newPageState: ObjectUpdatesEntry = state[url];
|
|
if (hasValue(newPageState)) {
|
|
const newUpdates: FieldUpdates = Object.assign({}, newPageState.fieldUpdates);
|
|
if (hasValue(newUpdates[uuid])) {
|
|
delete newUpdates[uuid];
|
|
}
|
|
const newFieldStates: FieldStates = Object.assign({}, newPageState.fieldStates);
|
|
if (hasValue(newFieldStates[uuid])) {
|
|
/* When resetting, make field not editable */
|
|
if (newFieldStates[uuid].isNew) {
|
|
/* If this field was added, just throw it away */
|
|
delete newFieldStates[uuid];
|
|
} else {
|
|
newFieldStates[uuid] = Object.assign({}, newFieldStates[uuid], { editable: false, isValid: true });
|
|
}
|
|
}
|
|
newPageState = Object.assign({}, state[url], {
|
|
fieldUpdates: newUpdates,
|
|
fieldStates: newFieldStates
|
|
});
|
|
}
|
|
return Object.assign({}, state, { [url]: newPageState });
|
|
}
|
|
|
|
/**
|
|
* Determine the most prominent FieldChangeType, ordered as follows:
|
|
* undefined < UPDATE < ADD < REMOVE
|
|
* @param oldType The current type
|
|
* @param newType The new type that should possibly override the new type
|
|
*/
|
|
function determineChangeType(oldType: FieldChangeType, newType: FieldChangeType): FieldChangeType {
|
|
if (hasNoValue(newType)) {
|
|
return oldType;
|
|
}
|
|
if (hasNoValue(oldType)) {
|
|
return newType;
|
|
}
|
|
return oldType.valueOf() > newType.valueOf() ? oldType : newType;
|
|
}
|
|
|
|
/**
|
|
* Set the editable state of a specific action's url and uuid to false or true
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function setEditableFieldUpdate(state: any, action: SetEditableFieldUpdateAction) {
|
|
const url: string = action.payload.url;
|
|
const uuid: string = action.payload.uuid;
|
|
const editable: boolean = action.payload.editable;
|
|
|
|
const pageState: ObjectUpdatesEntry = state[url];
|
|
|
|
const fieldState = pageState.fieldStates[uuid];
|
|
const newFieldState = Object.assign({}, fieldState, { editable });
|
|
|
|
const newFieldStates = Object.assign({}, pageState.fieldStates, { [uuid]: newFieldState });
|
|
|
|
const newPageState = Object.assign({}, pageState, { fieldStates: newFieldStates });
|
|
|
|
return Object.assign({}, state, { [url]: newPageState });
|
|
}
|
|
|
|
/**
|
|
* Set the isValid state of a specific action's url and uuid to false or true
|
|
* @param state The current state
|
|
* @param action The action to perform on the current state
|
|
*/
|
|
function setValidFieldUpdate(state: any, action: SetValidFieldUpdateAction) {
|
|
const url: string = action.payload.url;
|
|
const uuid: string = action.payload.uuid;
|
|
const isValid: boolean = action.payload.isValid;
|
|
|
|
const pageState: ObjectUpdatesEntry = state[url];
|
|
|
|
const fieldState = pageState.fieldStates[uuid];
|
|
const newFieldState = Object.assign({}, fieldState, { isValid });
|
|
|
|
const newFieldStates = Object.assign({}, pageState.fieldStates, { [uuid]: newFieldState });
|
|
|
|
const newPageState = Object.assign({}, pageState, { fieldStates: newFieldStates });
|
|
|
|
return Object.assign({}, state, { [url]: newPageState });
|
|
}
|
|
|
|
/**
|
|
* Method to create an initial FieldStates object based on a list of Identifiable objects
|
|
* @param fields Identifiable objects
|
|
*/
|
|
function createInitialFieldStates(fields: Identifiable[]) {
|
|
const uuids = fields.map((field: Identifiable) => field.uuid);
|
|
const fieldStates = {};
|
|
uuids.forEach((uuid: string) => fieldStates[uuid] = initialFieldState);
|
|
return fieldStates;
|
|
}
|