From 5040d230fb46568bceb5ca85a12ae7f3dce7d9eb Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 26 Sep 2018 17:30:24 +0200 Subject: [PATCH] 55693: (incomplete) store interraction for selecting items --- src/app/app.reducer.ts | 7 +- src/app/core/core.module.ts | 2 + .../shared/item-select/item-select.actions.ts | 75 +++++++++++++ .../item-select/item-select.component.html | 4 +- .../item-select/item-select.component.ts | 11 +- .../shared/item-select/item-select.reducer.ts | 84 +++++++++++++++ .../shared/item-select/item-select.service.ts | 101 ++++++++++++++++++ 7 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 src/app/shared/item-select/item-select.actions.ts create mode 100644 src/app/shared/item-select/item-select.reducer.ts create mode 100644 src/app/shared/item-select/item-select.service.ts diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index 8dc82dfb6f..ba882b50b8 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -14,6 +14,7 @@ import { } from './+search-page/search-filters/search-filter/search-filter.reducer'; import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers'; import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer'; +import { itemSelectionReducer, ItemSelectionsState } from './shared/item-select/item-select.reducer'; export interface AppState { router: fromRouter.RouterReducerState; @@ -23,7 +24,8 @@ export interface AppState { notifications: NotificationsState; searchSidebar: SearchSidebarState; searchFilter: SearchFiltersState; - truncatable: TruncatablesState; + truncatable: TruncatablesState, + itemSelection: ItemSelectionsState } export const appReducers: ActionReducerMap = { @@ -34,7 +36,8 @@ export const appReducers: ActionReducerMap = { notifications: notificationsReducer, searchSidebar: sidebarReducer, searchFilter: filterReducer, - truncatable: truncatableReducer + truncatable: truncatableReducer, + itemSelection: itemSelectionReducer }; export const routerStateSelector = (state: AppState) => state.router; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 73e97c7933..31b9b31244 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -64,6 +64,7 @@ import { NotificationsService } from '../shared/notifications/notifications.serv import { UploaderService } from '../shared/uploader/uploader.service'; import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service'; import { DSpaceObjectDataService } from './data/dspace-object-data.service'; +import { ItemSelectService } from '../shared/item-select/item-select.service'; const IMPORTS = [ CommonModule, @@ -128,6 +129,7 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, + ItemSelectService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/shared/item-select/item-select.actions.ts b/src/app/shared/item-select/item-select.actions.ts new file mode 100644 index 0000000000..0f17575a28 --- /dev/null +++ b/src/app/shared/item-select/item-select.actions.ts @@ -0,0 +1,75 @@ +import { type } from '../ngrx/type'; +import { Action } from '@ngrx/store'; + +export const ItemSelectionActionTypes = { + INITIAL_DESELECT: type('dspace/item-select/INITIAL_DESELECT'), + INITIAL_SELECT: type('dspace/item-select/INITIAL_SELECT'), + SELECT: type('dspace/item-select/SELECT'), + DESELECT: type('dspace/item-select/DESELECT'), + SWITCH: type('dspace/item-select/SWITCH'), + RESET: type('dspace/item-select/RESET') +}; + +export class ItemSelectionAction implements Action { + /** + * UUID of the item a select action can be performed on + */ + id: string; + + /** + * Type of action that will be performed + */ + type; + + /** + * Initialize with the item's UUID + * @param {string} id of the item + */ + constructor(id: string) { + this.id = id; + } +} + +/* tslint:disable:max-classes-per-file */ +/** + * Used to set the initial state to deselected + */ +export class ItemSelectionInitialDeselectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.INITIAL_DESELECT; +} + +/** + * Used to set the initial state to selected + */ +export class ItemSelectionInitialSelectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.INITIAL_SELECT; +} + +/** + * Used to select an item + */ +export class ItemSelectionSelectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.SELECT; +} + +/** + * Used to deselect an item + */ +export class ItemSelectionDeselectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.DESELECT; +} + +/** + * Used to switch an item between selected and deselected + */ +export class ItemSelectionSwitchAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.SWITCH; +} + +/** + * Used to reset all item's selected to be deselected + */ +export class ItemSelectionResetAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.RESET; +} +/* tslint:enable:max-classes-per-file */ diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index d92d87e156..c20e2cd6c1 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -16,8 +16,8 @@ - - + + {{(item.owningCollection | async)?.payload?.name}} {{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} {{item.findMetadata("dc.title")}} diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index e8ac0eb6f4..e24ce1c4fa 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -5,6 +5,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { Observable } from 'rxjs/Observable'; import { Item } from '../../core/shared/item.model'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { ItemSelectService } from './item-select.service'; @Component({ selector: 'ds-item-select', @@ -22,11 +23,19 @@ export class ItemSelectComponent implements OnInit { checked: boolean[] = []; - constructor(private itemDataService: ItemDataService) { + constructor(private itemSelectService: ItemSelectService) { } ngOnInit(): void { this.itemsRD$.subscribe((value) => console.log(value)); } + switch(id: string) { + this.itemSelectService.switch(id); + } + + getSelected(id: string): Observable { + return this.itemSelectService.getSelected(id); + } + } diff --git a/src/app/shared/item-select/item-select.reducer.ts b/src/app/shared/item-select/item-select.reducer.ts new file mode 100644 index 0000000000..1ea443850b --- /dev/null +++ b/src/app/shared/item-select/item-select.reducer.ts @@ -0,0 +1,84 @@ +import { isEmpty } from '../empty.util'; +import { ItemSelectionAction, ItemSelectionActionTypes } from './item-select.actions'; + +/** + * Interface that represents the state for a single filters + */ +export interface ItemSelectionState { + checked: boolean; +} + +/** + * Interface that represents the state for all available filters + */ +export interface ItemSelectionsState { + [id: string]: ItemSelectionState +} + +const initialState: ItemSelectionsState = Object.create(null); + +/** + * Performs a search filter action on the current state + * @param {SearchFiltersState} state The state before the action is performed + * @param {SearchFilterAction} action The action that should be performed + * @returns {SearchFiltersState} The state after the action is performed + */ +export function itemSelectionReducer(state = initialState, action: ItemSelectionAction): ItemSelectionsState { + + switch (action.type) { + + case ItemSelectionActionTypes.INITIAL_SELECT: { + if (isEmpty(state) || isEmpty(state[action.id])) { + return Object.assign({}, state, { + [action.id]: { + checked: true + } + }); + } + return state; + } + + case ItemSelectionActionTypes.INITIAL_DESELECT: { + if (isEmpty(state) || isEmpty(state[action.id])) { + return Object.assign({}, state, { + [action.id]: { + checked: false + } + }); + } + return state; + } + + case ItemSelectionActionTypes.SELECT: { + return Object.assign({}, state, { + [action.id]: { + checked: true + } + }); + } + + case ItemSelectionActionTypes.DESELECT: { + return Object.assign({}, state, { + [action.id]: { + checked: false + } + }); + } + + case ItemSelectionActionTypes.SWITCH: { + return Object.assign({}, state, { + [action.id]: { + checked: !state.checked + } + }); + } + + case ItemSelectionActionTypes.RESET: { + return {}; + } + + default: { + return state; + } + } +} diff --git a/src/app/shared/item-select/item-select.service.ts b/src/app/shared/item-select/item-select.service.ts new file mode 100644 index 0000000000..3ba9f9a579 --- /dev/null +++ b/src/app/shared/item-select/item-select.service.ts @@ -0,0 +1,101 @@ +import { Injectable } from '@angular/core'; +import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; +import { ItemSelectionsState, ItemSelectionState } from './item-select.reducer'; +import { + ItemSelectionDeselectAction, + ItemSelectionInitialDeselectAction, + ItemSelectionInitialSelectAction, ItemSelectionResetAction, + ItemSelectionSelectAction, ItemSelectionSwitchAction +} from './item-select.actions'; +import { Observable } from 'rxjs/Observable'; +import { hasValue } from '../empty.util'; + +const selectionStateSelector = (state: ItemSelectionsState) => state.selectionItem; + +/** + * Service that takes care of selecting and deselecting items + */ +@Injectable() +export class ItemSelectService { + + constructor(private store: Store) { + } + + /** + * Request the current selection of a given item + * @param {string} id The UUID of the item + * @returns {Observable} Emits the current selection state of the given item, if it's unavailable, return false + */ + getSelected(id: string): Observable { + return this.store.select(selectionByIdSelector(id)) + .map((object: ItemSelectionState) => { + if (object) { + return object.checked; + } else { + return false; + } + }); + } + + /** + * Dispatches an initial select action to the store for a given item + * @param {string} id The UUID of the item to select + */ + public initialSelect(id: string): void { + this.store.dispatch(new ItemSelectionInitialSelectAction(id)); + } + + /** + * Dispatches an initial deselect action to the store for a given item + * @param {string} id The UUID of the item to deselect + */ + public initialDeselect(id: string): void { + this.store.dispatch(new ItemSelectionInitialDeselectAction(id)); + } + + /** + * Dispatches a select action to the store for a given item + * @param {string} id The UUID of the item to select + */ + public select(id: string): void { + this.store.dispatch(new ItemSelectionSelectAction(id)); + } + + /** + * Dispatches a deselect action to the store for a given item + * @param {string} id The UUID of the item to deselect + */ + public deselect(id: string): void { + this.store.dispatch(new ItemSelectionDeselectAction(id)); + } + + /** + * Dispatches a switch action to the store for a given item + * @param {string} id The UUID of the item to select + */ + public switch(id: string): void { + this.store.dispatch(new ItemSelectionSwitchAction(id)); + } + + /** + * Dispatches a reset action to the store for all items + */ + public reset(): void { + this.store.dispatch(new ItemSelectionResetAction(null)); + } + +} + +function selectionByIdSelector(id: string): MemoizedSelector { + return keySelector(id); +} + +export function keySelector(key: string): MemoizedSelector { + return createSelector(selectionStateSelector, (state: ItemSelectionState) => { + if (hasValue(state)) { + return state[key]; + } else { + return undefined; + } + }); +}