progress july 11 - store/selection to object list

This commit is contained in:
lotte
2019-07-11 15:09:28 +02:00
parent 42c690dfd4
commit b1585ac7f2
14 changed files with 520 additions and 201 deletions

View File

@@ -0,0 +1,87 @@
import { Action } from '@ngrx/store';
import { type } from '../../ngrx/type';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
/**
* For each action type in an action group, make a simple
* enum object for all of this group's action types.
*
* The 'type' utility function coerces strings into string
* literal types and runs a simple check to guarantee all
* action types in the application are unique.
*/
export const SelectableListActionTypes = {
SELECT: type('dspace/selectable-lists/SELECT'),
SELECT_SINGLE: type('dspace/selectable-lists/SELECT_SINGLE'),
DESELECT: type('dspace/selectable-lists/DESELECT'),
DESELECT_SINGLE: type('dspace/selectable-lists/DESELECT_SINGLE'),
SET_SELECTION: type('dspace/selectable-lists/SET_SELECTION'),
DESELECT_ALL: type('dspace/selectable-lists/DESELECT_ALL')
};
/* tslint:disable:max-classes-per-file */
export abstract class SelectableListAction implements Action {
constructor(public type, public id: string) {
}
}
/**
* Used to select an item in a the selectable list
*/
export class SelectableListSelectAction extends SelectableListAction {
payload: ListableObject[];
constructor(id: string, objects: ListableObject[]) {
super(SelectableListActionTypes.SELECT_SINGLE, id);
this.payload = objects;
}
}
export class SelectableListSelectSingleAction extends SelectableListAction {
payload: {
object: ListableObject,
multipleSelectionsAllowed: boolean
};
constructor(id: string, object: ListableObject, multipleSelectionsAllowed: boolean = true) {
super(SelectableListActionTypes.SELECT, id);
this.payload = { object, multipleSelectionsAllowed };
}
}
export class SelectableListDeselectSingleAction extends SelectableListAction {
payload: ListableObject;
constructor(id: string, object: ListableObject) {
super(SelectableListActionTypes.DESELECT_SINGLE, id);
this.payload = object;
}
}
export class SelectableListDeselectAction extends SelectableListAction {
payload: ListableObject[];
constructor(id: string, objects: ListableObject[]) {
super(SelectableListActionTypes.DESELECT, id);
this.payload = objects;
}
}
export class SelectableListSetSelectionAction extends SelectableListAction {
payload: ListableObject;
constructor(id: string, objects: ListableObject[]) {
super(SelectableListActionTypes.SET_SELECTION, id);
this.payload = objects;
}
}
export class SelectableListDeselectAllAction extends SelectableListAction {
constructor(id: string) {
super(SelectableListActionTypes.DESELECT_ALL, id);
}
}
/* tslint:enable:max-classes-per-file */

View File

@@ -0,0 +1,106 @@
import { ListableObject } from '../../object-collection/shared/listable-object.model';
import {
SelectableListAction,
SelectableListActionTypes,
SelectableListSelectAction,
SelectableListSelectSingleAction,
SelectableListDeselectAction,
SelectableListDeselectSingleAction, SelectableListSetSelectionAction
} from './selectable-list.actions';
import { hasNoValue } from '../../empty.util';
/**
* Represents the state of all selectable lists in the store
*/
export type SelectableListsState = {
[id: string]: SelectableListState;
}
/**
* Represents the state of a single selectable list in the store
*/
export interface SelectableListState {
id: string;
selection: ListableObject[];
}
/**
* Reducer that handles SelectableListAction to update the SelectableListsState
* @param {SelectableListsState} state The initial SelectableListsState
* @param {SelectableListAction} action The Action to be performed on the state
* @returns {SelectableListsState} The new, reducer SelectableListsState
*/
export function selectableListReducer(state: SelectableListsState = {}, action: SelectableListAction): SelectableListsState {
const listState: SelectableListState = state[action.id] || clearSelection(action.id);
switch (action.type) {
case SelectableListActionTypes.SELECT: {
const newListState = select(listState, action as SelectableListSelectAction);
return Object.assign({}, state, { [action.id]: newListState });
}
case SelectableListActionTypes.SELECT_SINGLE: {
const newListState = selectSingle(listState, action as SelectableListSelectSingleAction);
return Object.assign({}, state, { [action.id]: newListState });
}
case SelectableListActionTypes.DESELECT: {
const newListState = deselect(listState, action as SelectableListDeselectAction);
return Object.assign({}, state, { [action.id]: newListState });
}
case SelectableListActionTypes.DESELECT_SINGLE: {
const newListState = deselectSingle(listState, action as SelectableListDeselectSingleAction);
return Object.assign({}, state, { [action.id]: newListState });
}
case SelectableListActionTypes.SET_SELECTION: {
const newListState = setList(listState, action as SelectableListSetSelectionAction);
return Object.assign({}, state, { [action.id]: newListState });
}
case SelectableListActionTypes.DESELECT_ALL: {
const newListState = clearSelection(action.id);
return Object.assign({}, state, { [action.id]: newListState });
}
default: {
return state;
}
}
}
function select(state: SelectableListState, action: SelectableListSelectAction) {
const filteredNewObjects = action.payload.filter((object) => !isObjectInSelection(state.selection, object));
const newSelection = [...state.selection, ...filteredNewObjects];
return Object.assign({}, state, { selection: newSelection });
}
function selectSingle(state: SelectableListState, action: SelectableListSelectSingleAction) {
let newSelection;
if (action.payload.multipleSelectionsAllowed && !isObjectInSelection(state.selection, action.payload)) {
newSelection = [...state.selection, action.payload.object];
} else {
newSelection = [action.payload.object];
}
return Object.assign({}, state, { selection: newSelection });
}
function deselect(state: SelectableListState, action: SelectableListDeselectAction) {
const newSelection = state.selection.filter((selected) => hasNoValue(action.payload.find((object) => object === selected)));
return Object.assign({}, state, { selection: newSelection });
}
function deselectSingle(state: SelectableListState, action: SelectableListDeselectSingleAction) {
const newSelection = state.selection.filter((selected) => {
return selected !== action.payload
});
return Object.assign({}, state, { selection: newSelection });
}
function setList(state: SelectableListState, action: SelectableListSetSelectionAction) {
const newSelection = [...state.selection, action.payload];
return Object.assign({}, state, { selection: newSelection });
}
function clearSelection(id: string) {
return { id: id, selection: [] };
}
function isObjectInSelection(selection: ListableObject[], object: ListableObject) {
return selection.findIndex((selected) => selected === object) >= 0
}

View File

@@ -0,0 +1,94 @@
import { Injectable } from '@angular/core';
import { MemoizedSelector, select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, startWith, tap } from 'rxjs/operators';
import { SelectableListsState, SelectableListState } from './selectable-list.reducer';
import { AppState, keySelector } from '../../../app.reducer';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
import {
SelectableListDeselectAction, SelectableListDeselectAllAction,
SelectableListDeselectSingleAction, SelectableListSelectAction,
SelectableListSelectSingleAction
} from './selectable-list.actions';
import { hasNoValue, hasValue, isNotEmpty } from '../../empty.util';
const selectableListsStateSelector = (state) => state.selectableLists;
const menuByIDSelector = (id: string): MemoizedSelector<AppState, SelectableListState> => {
return keySelector<SelectableListState>(id, selectableListsStateSelector);
};
@Injectable()
export class SelectableListService {
constructor(private store: Store<SelectableListsState>) {
}
/**
* Retrieve a selectable list's state by its ID
* @param {string} id ID of the requested Selectable list
* @returns {Observable<SelectableListState>} Observable that emits the current state of the requested selectable list
*/
getSelectableList(id: string): Observable<SelectableListState> {
return this.store.pipe(select(menuByIDSelector(id)));
}
/**
* Select an object in a specific list in the store
* @param {string} id The id of the list on which the object should be selected
* @param {ListableObject} object The object to select
*/
selectSingle(id: string, object: ListableObject, multipleSelectionsAllowed?) {
this.store.dispatch(new SelectableListSelectSingleAction(id, object, multipleSelectionsAllowed));
}
/**
* Select multiple objects in a specific list in the store
* @param {string} id The id of the list on which the objects should be selected
* @param {ListableObject[]} objects The objects to select
*/
select(id: string, objects: ListableObject[]) {
this.store.dispatch(new SelectableListSelectAction(id, objects));
}
/**
* Deselect an object in a specific list in the store
* @param {string} id The id of the list on which the object should be deselected
* @param {ListableObject} object The object to deselect
*/
deselectSingle(id: string, object: ListableObject) {
this.store.dispatch(new SelectableListDeselectSingleAction(id, object));
}
/**
* Deselect multiple objects in a specific list in the store
* @param {string} id The id of the list on which the objects should be deselected
* @param {ListableObject[]} objects The objects to deselect
*/
deselect(id: string, objects: ListableObject[]) {
this.store.dispatch(new SelectableListDeselectAction(id, objects));
}
/**
* Deselect all objects in a specific list in the store
* @param {string} id The id of the list on which the objects should be deselected
*/
deselectAll(id: string) {
this.store.dispatch(new SelectableListDeselectAllAction(id));
}
/**
* Check if a given object is selected in a specific list
* @param {string} id The ID of the selectable list the object should be selected in
* @param {ListableObject} object The object to check for if it's selected
* @returns {Observable<boolean>} Emits true if the given object is selected, emits false when it's deselected
*/
isObjectSelected(id: string, object: ListableObject): Observable<boolean> {
return this.getSelectableList(id).pipe(
filter((state: SelectableListState) => hasValue(state)),
map((state: SelectableListState) => isNotEmpty(state.selection) && hasValue(state.selection.find((selected) => selected === object))),
startWith(false),
distinctUntilChanged()
);
}
}