From 8fabd98afb15ef160f8f21e9ba1cef50e893c9be Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 26 Nov 2019 21:12:44 -0500 Subject: [PATCH] [Issue #49] Add the Collection state wiring. --- .../app/library/actions/collection.actions.ts | 84 +++++++ .../adaptors/collection.adaptor.spec.ts | 210 ++++++++++++++++ .../library/adaptors/collection.adaptor.ts | 111 +++++++++ .../effects/collection.effects.spec.ts | 226 ++++++++++++++++++ .../app/library/effects/collection.effects.ts | 123 ++++++++++ comixed-frontend/src/app/library/index.ts | 6 +- .../src/app/library/library.constants.ts | 3 + .../src/app/library/library.module.ts | 15 +- .../models/collection-entry.fixtures.ts | 44 ++++ .../app/library/models/collection-entry.ts | 22 ++ .../library/models/collection-type.enum.ts | 26 ++ .../models/net/get-collection-page-request.ts | 26 ++ .../net/get-collection-page-response.ts | 24 ++ .../reducers/collection.reducer.spec.ts | 212 ++++++++++++++++ .../library/reducers/collection.reducer.ts | 86 +++++++ .../services/collection.service.spec.ts | 124 ++++++++++ .../library/services/collection.service.ts | 60 +++++ .../src/assets/i18n/library-en.json | 12 + 18 files changed, 1411 insertions(+), 3 deletions(-) create mode 100644 comixed-frontend/src/app/library/actions/collection.actions.ts create mode 100644 comixed-frontend/src/app/library/adaptors/collection.adaptor.spec.ts create mode 100644 comixed-frontend/src/app/library/adaptors/collection.adaptor.ts create mode 100644 comixed-frontend/src/app/library/effects/collection.effects.spec.ts create mode 100644 comixed-frontend/src/app/library/effects/collection.effects.ts create mode 100644 comixed-frontend/src/app/library/models/collection-entry.fixtures.ts create mode 100644 comixed-frontend/src/app/library/models/collection-entry.ts create mode 100644 comixed-frontend/src/app/library/models/collection-type.enum.ts create mode 100644 comixed-frontend/src/app/library/models/net/get-collection-page-request.ts create mode 100644 comixed-frontend/src/app/library/models/net/get-collection-page-response.ts create mode 100644 comixed-frontend/src/app/library/reducers/collection.reducer.spec.ts create mode 100644 comixed-frontend/src/app/library/reducers/collection.reducer.ts create mode 100644 comixed-frontend/src/app/library/services/collection.service.spec.ts create mode 100644 comixed-frontend/src/app/library/services/collection.service.ts diff --git a/comixed-frontend/src/app/library/actions/collection.actions.ts b/comixed-frontend/src/app/library/actions/collection.actions.ts new file mode 100644 index 000000000..1baef6699 --- /dev/null +++ b/comixed-frontend/src/app/library/actions/collection.actions.ts @@ -0,0 +1,84 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Action } from '@ngrx/store'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { CollectionEntry } from 'app/library/models/collection-entry'; +import { Comic } from 'app/comics'; + +export enum CollectionActionTypes { + Load = '[COLLECTION] Load a collection', + Received = '[COLLECTION] Collection received', + LoadFailed = '[COLLECTION] Failed to load a collection', + GetComics = '[COLLECTION] Get comics for a collection', + ComicsReceived = '[COLLECTION] Comics for a collection received', + GetComicsFailed = '[COLLECTION] Failed to get comics for a collection' +} + +export class CollectionLoad implements Action { + readonly type = CollectionActionTypes.Load; + + constructor(public payload: { collectionType: CollectionType }) {} +} + +export class CollectionReceived implements Action { + readonly type = CollectionActionTypes.Received; + + constructor(public payload: { entries: CollectionEntry[] }) {} +} + +export class CollectionLoadFailed implements Action { + readonly type = CollectionActionTypes.LoadFailed; + + constructor() {} +} + +export class CollectionGetComics implements Action { + readonly type = CollectionActionTypes.GetComics; + + constructor( + public payload: { + collectionType: CollectionType; + name: string; + page: number; + count: number; + sortField: string; + ascending: boolean; + } + ) {} +} + +export class CollectionComicsReceived implements Action { + readonly type = CollectionActionTypes.ComicsReceived; + + constructor(public payload: { comics: Comic[]; comicCount: number }) {} +} + +export class CollectionGetComicsFailed implements Action { + readonly type = CollectionActionTypes.GetComicsFailed; + + constructor() {} +} + +export type CollectionActions = + | CollectionLoad + | CollectionReceived + | CollectionLoadFailed + | CollectionGetComics + | CollectionComicsReceived + | CollectionGetComicsFailed; diff --git a/comixed-frontend/src/app/library/adaptors/collection.adaptor.spec.ts b/comixed-frontend/src/app/library/adaptors/collection.adaptor.spec.ts new file mode 100644 index 000000000..3a2c7a21b --- /dev/null +++ b/comixed-frontend/src/app/library/adaptors/collection.adaptor.spec.ts @@ -0,0 +1,210 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { CollectionAdaptor } from './collection.adaptor'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 +} from 'app/library/models/collection-entry.fixtures'; +import { COMIC_1, COMIC_2, COMIC_3 } from 'app/comics/models/comic.fixtures'; +import { TestBed } from '@angular/core/testing'; +import { Store, StoreModule } from '@ngrx/store'; +import { + COLLECTION_FEATURE_KEY, + reducer +} from 'app/library/reducers/collection.reducer'; +import { EffectsModule } from '@ngrx/effects'; +import { CollectionEffects } from 'app/library/effects/collection.effects'; +import { AppState } from 'app/library'; +import { + CollectionComicsReceived, + CollectionGetComics, + CollectionGetComicsFailed, + CollectionLoad, + CollectionLoadFailed, + CollectionReceived +} from 'app/library/actions/collection.actions'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { MessageService } from 'primeng/api'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('CollectionAdaptor', () => { + const COLLECTION_TYPE = CollectionType.STORIES; + const COLLECTION_ENTRIES = [ + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 + ]; + const COLLECTION_NAME = 'Fancy Publisher'; + const PAGE = 17; + const COUNT = 25; + const SORT_FIELD = 'addedDate'; + const ASCENDING = false; + const COMICS = [COMIC_1, COMIC_2, COMIC_3]; + const COMIC_COUNT = 3; + + let adaptor: CollectionAdaptor; + let store: Store; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + TranslateModule.forRoot(), + StoreModule.forRoot({}), + StoreModule.forFeature(COLLECTION_FEATURE_KEY, reducer), + EffectsModule.forRoot([]), + EffectsModule.forFeature([CollectionEffects]) + ], + providers: [CollectionAdaptor, MessageService] + }); + + adaptor = TestBed.get(CollectionAdaptor); + store = TestBed.get(Store); + spyOn(store, 'dispatch').and.callThrough(); + }); + + it('should create an instance', () => { + expect(adaptor).toBeTruthy(); + }); + + describe('getting all entries for a collection', () => { + beforeEach(() => { + adaptor.getCollection(COLLECTION_TYPE); + }); + + it('fires an action', () => { + expect(store.dispatch).toHaveBeenCalledWith( + new CollectionLoad({ collectionType: COLLECTION_TYPE }) + ); + }); + + it('provides updates', () => { + adaptor.fetchingEntries$.subscribe(response => + expect(response).toBeTruthy() + ); + }); + + describe('success', () => { + beforeEach(() => { + store.dispatch(new CollectionReceived({ entries: COLLECTION_ENTRIES })); + }); + + it('provides updates', () => { + adaptor.fetchingEntries$.subscribe(response => + expect(response).toBeFalsy() + ); + }); + + it('updates the entries', () => { + adaptor.entries$.subscribe(response => + expect(response).toEqual(COLLECTION_ENTRIES) + ); + }); + }); + + describe('failure', () => { + beforeEach(() => { + store.dispatch(new CollectionLoadFailed()); + }); + + it('provides updates', () => { + adaptor.fetchingEntries$.subscribe(response => + expect(response).toBeFalsy() + ); + }); + }); + }); + + describe('getting a page of comics for a collection', () => { + beforeEach(() => { + adaptor.getPageForEntry( + COLLECTION_TYPE, + COLLECTION_NAME, + PAGE, + COUNT, + SORT_FIELD, + ASCENDING + ); + }); + + it('fires an action', () => { + expect(store.dispatch).toHaveBeenCalledWith( + new CollectionGetComics({ + collectionType: COLLECTION_TYPE, + name: COLLECTION_NAME, + page: PAGE, + count: COUNT, + sortField: SORT_FIELD, + ascending: ASCENDING + }) + ); + }); + + it('provides updates', () => { + adaptor.fetchingEntry$.subscribe(response => + expect(response).toBeTruthy() + ); + }); + + describe('success', () => { + beforeEach(() => { + store.dispatch( + new CollectionComicsReceived({ + comics: COMICS, + comicCount: COMIC_COUNT + }) + ); + }); + + it('provides updates', () => { + adaptor.fetchingEntry$.subscribe(response => + expect(response).toBeFalsy() + ); + }); + + it('updates the list of comics', () => { + adaptor.comics$.subscribe(response => expect(response).toEqual(COMICS)); + }); + + it('updates the comic count', () => { + adaptor.comicCount$.subscribe(response => + expect(response).toEqual(COMIC_COUNT) + ); + }); + }); + + describe('failure', () => { + beforeEach(() => { + store.dispatch(new CollectionGetComicsFailed()); + }); + + it('provides updates', () => { + adaptor.fetchingEntry$.subscribe(response => + expect(response).toBeFalsy() + ); + }); + }); + }); +}); diff --git a/comixed-frontend/src/app/library/adaptors/collection.adaptor.ts b/comixed-frontend/src/app/library/adaptors/collection.adaptor.ts new file mode 100644 index 000000000..02b4626e7 --- /dev/null +++ b/comixed-frontend/src/app/library/adaptors/collection.adaptor.ts @@ -0,0 +1,111 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from 'app/library'; +import { + COLLECTION_FEATURE_KEY, + CollectionState +} from 'app/library/reducers/collection.reducer'; +import { filter } from 'rxjs/operators'; +import * as _ from 'lodash'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { CollectionEntry } from 'app/library/models/collection-entry'; +import { + CollectionGetComics, + CollectionLoad +} from 'app/library/actions/collection.actions'; +import { Comic } from 'app/comics'; + +@Injectable() +export class CollectionAdaptor { + private _fetchingEntries$ = new BehaviorSubject(false); + private _entries$ = new BehaviorSubject([]); + private _fetchingEntry$ = new BehaviorSubject(false); + private _comics$ = new BehaviorSubject([]); + private _comicCount$ = new BehaviorSubject(0); + + constructor(private store: Store) { + this.store + .select(COLLECTION_FEATURE_KEY) + .pipe(filter(state => !!state)) + .subscribe((state: CollectionState) => { + if (state.fetchingEntries !== this._fetchingEntries$.getValue()) { + this._fetchingEntries$.next(state.fetchingEntries); + } + if (!_.isEqual(state.entries, this._entries$.getValue())) { + this._entries$.next(state.entries); + } + if (state.fetchingEntry !== this._fetchingEntry$.getValue()) { + this._fetchingEntry$.next(state.fetchingEntry); + } + if (!_.isEqual(state.comics, this._comics$.getValue())) { + this._comics$.next(state.comics); + } + if (state.comicCount !== this._comicCount$.getValue()) { + this._comicCount$.next(state.comicCount); + } + }); + } + + getCollection(collectionType: CollectionType): void { + this.store.dispatch(new CollectionLoad({ collectionType: collectionType })); + } + + get fetchingEntries$(): Observable { + return this._fetchingEntries$.asObservable(); + } + + get entries$(): Observable { + return this._entries$.asObservable(); + } + + getPageForEntry( + collectionType: CollectionType, + name: string, + page: number, + count: number, + sortField: string, + ascending: boolean + ): void { + this.store.dispatch( + new CollectionGetComics({ + collectionType: collectionType, + name: name, + page: page, + count: count, + sortField: sortField, + ascending: ascending + }) + ); + } + + get fetchingEntry$(): Observable { + return this._fetchingEntry$.asObservable(); + } + + get comics$(): Observable { + return this._comics$.asObservable(); + } + + get comicCount$(): Observable { + return this._comicCount$.asObservable(); + } +} diff --git a/comixed-frontend/src/app/library/effects/collection.effects.spec.ts b/comixed-frontend/src/app/library/effects/collection.effects.spec.ts new file mode 100644 index 000000000..38a935283 --- /dev/null +++ b/comixed-frontend/src/app/library/effects/collection.effects.spec.ts @@ -0,0 +1,226 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Observable, of, throwError } from 'rxjs'; + +import { CollectionEffects } from './collection.effects'; +import { CollectionService } from 'app/library/services/collection.service'; +import { MessageService } from 'primeng/api'; +import { TranslateModule } from '@ngx-translate/core'; +import { StoreModule } from '@ngrx/store'; +import { + COLLECTION_FEATURE_KEY, + reducer +} from 'app/library/reducers/collection.reducer'; +import { EffectsModule } from '@ngrx/effects'; +import { + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 +} from 'app/library/models/collection-entry.fixtures'; +import { + CollectionComicsReceived, + CollectionGetComics, + CollectionGetComicsFailed, + CollectionLoad, + CollectionLoadFailed, + CollectionReceived +} from 'app/library/actions/collection.actions'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { hot } from 'jasmine-marbles'; +import { HttpErrorResponse } from '@angular/common/http'; +import { COMIC_1, COMIC_2, COMIC_3 } from 'app/comics/models/comic.fixtures'; +import { GetCollectionPageResponse } from 'app/library/models/net/get-collection-page-response'; +import objectContaining = jasmine.objectContaining; + +describe('CollectionEffects', () => { + const COLLECTION_TYPE = CollectionType.CHARACTERS; + const COLLECTION_ENTRIES = [ + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 + ]; + const COLLECTION_NAME = 'Fancy Publisher'; + const PAGE = 17; + const COUNT = 25; + const SORT_FIELD = 'addedDate'; + const ASCENDING = false; + const COMICS = [COMIC_1, COMIC_2, COMIC_3]; + const COMIC_COUNT = 3; + + let actions$: Observable; + let effects: CollectionEffects; + let collectionService: jasmine.SpyObj; + let messageService: MessageService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + StoreModule.forRoot({}), + StoreModule.forFeature(COLLECTION_FEATURE_KEY, reducer), + EffectsModule.forRoot([]), + EffectsModule.forFeature([CollectionEffects]) + ], + providers: [ + CollectionEffects, + provideMockActions(() => actions$), + { + provide: CollectionService, + useValue: { + getEntries: jasmine.createSpy('CollectionService.getEntries()'), + getPageForEntry: jasmine.createSpy( + 'CollectionService.getPageForEntry()' + ) + } + }, + MessageService + ] + }); + + effects = TestBed.get(CollectionEffects); + collectionService = TestBed.get(CollectionService); + messageService = TestBed.get(MessageService); + spyOn(messageService, 'add'); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('getting all entries for a collection', () => { + it('fires an action on success', () => { + const serviceResponse = COLLECTION_ENTRIES; + const action = new CollectionLoad({ collectionType: COLLECTION_TYPE }); + const outcome = new CollectionReceived({ entries: COLLECTION_ENTRIES }); + + actions$ = hot('-a', { a: action }); + collectionService.getEntries.and.returnValue(of(serviceResponse)); + + const expected = hot('-b', { b: outcome }); + expect(effects.getEntries$).toBeObservable(expected); + }); + + it('fires an action on service failure', () => { + const serviceResponse = new HttpErrorResponse({}); + const action = new CollectionLoad({ collectionType: COLLECTION_TYPE }); + const outcome = new CollectionLoadFailed(); + + actions$ = hot('-a', { a: action }); + collectionService.getEntries.and.returnValue(throwError(serviceResponse)); + + const expected = hot('-b', { b: outcome }); + expect(effects.getEntries$).toBeObservable(expected); + expect(messageService.add).toHaveBeenCalledWith( + objectContaining({ severity: 'error' }) + ); + }); + + it('fires an action on general failure', () => { + const action = new CollectionLoad({ collectionType: COLLECTION_TYPE }); + const outcome = new CollectionLoadFailed(); + + actions$ = hot('-a', { a: action }); + collectionService.getEntries.and.throwError('expect'); + + const expected = hot('-(b|)', { b: outcome }); + expect(effects.getEntries$).toBeObservable(expected); + expect(messageService.add).toHaveBeenCalledWith( + objectContaining({ severity: 'error' }) + ); + }); + }); + + describe('getting comics for a collection type', () => { + it('fires an action on success', () => { + const serviceResponse = { + comics: COMICS, + comicCount: COMIC_COUNT + } as GetCollectionPageResponse; + const action = new CollectionGetComics({ + collectionType: COLLECTION_TYPE, + name: COLLECTION_NAME, + page: PAGE, + count: COUNT, + sortField: SORT_FIELD, + ascending: ASCENDING + }); + const outcome = new CollectionComicsReceived({ + comics: COMICS, + comicCount: COMIC_COUNT + }); + + actions$ = hot('-a', { a: action }); + collectionService.getPageForEntry.and.returnValue(of(serviceResponse)); + + const expected = hot('-b', { b: outcome }); + expect(effects.getPageForEntry$).toBeObservable(expected); + }); + + it('fires an action on service failure', () => { + const serviceResponse = new HttpErrorResponse({}); + const action = new CollectionGetComics({ + collectionType: COLLECTION_TYPE, + name: COLLECTION_NAME, + page: PAGE, + count: COUNT, + sortField: SORT_FIELD, + ascending: ASCENDING + }); + const outcome = new CollectionGetComicsFailed(); + + actions$ = hot('-a', { a: action }); + collectionService.getPageForEntry.and.returnValue( + throwError(serviceResponse) + ); + + const expected = hot('-b', { b: outcome }); + expect(effects.getPageForEntry$).toBeObservable(expected); + expect(messageService.add).toHaveBeenCalledWith( + objectContaining({ severity: 'error' }) + ); + }); + + it('fires an action on general failure', () => { + const action = new CollectionGetComics({ + collectionType: COLLECTION_TYPE, + name: COLLECTION_NAME, + page: PAGE, + count: COUNT, + sortField: SORT_FIELD, + ascending: ASCENDING + }); + const outcome = new CollectionGetComicsFailed(); + + actions$ = hot('-a', { a: action }); + collectionService.getPageForEntry.and.throwError('expect'); + + const expected = hot('-(b|)', { b: outcome }); + expect(effects.getPageForEntry$).toBeObservable(expected); + expect(messageService.add).toHaveBeenCalledWith( + objectContaining({ severity: 'error' }) + ); + }); + }); +}); diff --git a/comixed-frontend/src/app/library/effects/collection.effects.ts b/comixed-frontend/src/app/library/effects/collection.effects.ts new file mode 100644 index 000000000..65906a2a6 --- /dev/null +++ b/comixed-frontend/src/app/library/effects/collection.effects.ts @@ -0,0 +1,123 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { + CollectionActions, + CollectionActionTypes, + CollectionComicsReceived, + CollectionGetComicsFailed, + CollectionLoadFailed, + CollectionReceived +} from '../actions/collection.actions'; +import { Observable, of } from 'rxjs'; +import { Action } from '@ngrx/store'; +import { catchError, map, switchMap } from 'rxjs/operators'; +import { CollectionService } from 'app/library/services/collection.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MessageService } from 'primeng/api'; +import { GetCollectionPageResponse } from 'app/library/models/net/get-collection-page-response'; +import { CollectionEntry } from 'app/library/models/collection-entry'; + +@Injectable() +export class CollectionEffects { + constructor( + private actions$: Actions, + private translateService: TranslateService, + private messageService: MessageService, + private collectionService: CollectionService + ) {} + + @Effect() + getEntries$: Observable = this.actions$.pipe( + ofType(CollectionActionTypes.Load), + map(action => action.payload), + switchMap(action => + this.collectionService.getEntries(action.collectionType).pipe( + map( + (response: CollectionEntry[]) => + new CollectionReceived({ entries: response }) + ), + catchError(error => { + this.messageService.add({ + severity: 'error', + detail: this.translateService.instant( + 'collection-effects.get-entries.error.detail', + { collectionType: action.collectionType } + ) + }); + return of(new CollectionLoadFailed()); + }) + ) + ), + catchError(error => { + this.messageService.add({ + severity: 'error', + detail: this.translateService.instant( + 'general-message.error.general-service-failure' + ) + }); + return of(new CollectionLoadFailed()); + }) + ); + + @Effect() + getPageForEntry$: Observable = this.actions$.pipe( + ofType(CollectionActionTypes.GetComics), + map(action => action.payload), + switchMap(action => + this.collectionService + .getPageForEntry( + action.collectionType, + action.name, + action.page, + action.count, + action.sortField, + action.ascending + ) + .pipe( + map( + (response: GetCollectionPageResponse) => + new CollectionComicsReceived({ + comics: response.comics, + comicCount: response.comicCount + }) + ), + catchError(error => { + this.messageService.add({ + severity: 'error', + detail: this.translateService.instant( + 'collection-effects.get-page-for-entry.error.detail' + ) + }); + return of(new CollectionGetComicsFailed()); + }) + ) + ), + catchError(error => { + this.messageService.add({ + severity: 'error', + detail: this.translateService.instant( + 'general-message.error.general-service-failure' + ) + }); + return of(new CollectionGetComicsFailed()); + }) + ); +} diff --git a/comixed-frontend/src/app/library/index.ts b/comixed-frontend/src/app/library/index.ts index 80c7dd25f..b81ff9e0b 100644 --- a/comixed-frontend/src/app/library/index.ts +++ b/comixed-frontend/src/app/library/index.ts @@ -23,11 +23,13 @@ import * as fromSelection from './reducers/selection.reducer'; import { SelectionState } from './reducers/selection.reducer'; import * as fromFilters from './reducers/filters.reducer'; import * as fromDupePages from './reducers/duplicate-pages.reducer'; +import * as fromCollections from './reducers/collection.reducer'; import { Params } from '@angular/router'; import { ActionReducerMap, MetaReducer } from '@ngrx/store'; import { environment } from '../../environments/environment'; import { FilterState } from 'app/library/reducers/filters.reducer'; import { DuplicatePagesState } from 'app/library/reducers/duplicate-pages.reducer'; +import { CollectionState } from 'app/library/reducers/collection.reducer'; export { LibraryAdaptor } from './adaptors/library.adaptor'; export { SelectionAdaptor } from './adaptors/selection.adaptor'; @@ -46,6 +48,7 @@ export interface AppState { selection_state: SelectionState; filters_state: FilterState; duplicate_pages_state: DuplicatePagesState; + collection_state: CollectionState; } export type State = AppState; @@ -55,7 +58,8 @@ export const reducers: ActionReducerMap = { library: fromLibrary.reducer, selection_state: fromSelection.reducer, filters_state: fromFilters.reducer, - duplicate_pages_state: fromDupePages.reducer + duplicate_pages_state: fromDupePages.reducer, + collection_state: fromCollections.reducer }; export const metaReducers: MetaReducer[] = !environment.production diff --git a/comixed-frontend/src/app/library/library.constants.ts b/comixed-frontend/src/app/library/library.constants.ts index 198f4c262..54302c633 100644 --- a/comixed-frontend/src/app/library/library.constants.ts +++ b/comixed-frontend/src/app/library/library.constants.ts @@ -20,3 +20,6 @@ import { API_ROOT_URL } from 'app/app.functions'; export const GET_ALL_DUPLICATE_PAGES_URL = `${API_ROOT_URL}/pages/duplicates`; export const SET_BLOCKING_STATE_URL = `${API_ROOT_URL}/pages/hashes/blocking`; + +export const GET_COLLECTION_ENTRIES_URL = `${API_ROOT_URL}/collections/\${type}`; +export const GET_PAGE_FOR_ENTRY_URL = `${API_ROOT_URL}/collections/\${type}/\${name}`; diff --git a/comixed-frontend/src/app/library/library.module.ts b/comixed-frontend/src/app/library/library.module.ts index 9ba680e0b..3ca50862b 100644 --- a/comixed-frontend/src/app/library/library.module.ts +++ b/comixed-frontend/src/app/library/library.module.ts @@ -29,6 +29,7 @@ import * as fromSelection from './reducers/selection.reducer'; import * as fromReadingList from './reducers/reading-list.reducer'; import * as fromFilters from './reducers/filters.reducer'; import * as fromDupes from './reducers/duplicate-pages.reducer'; +import * as fromCollections from './reducers/collection.reducer'; import { EffectsModule } from '@ngrx/effects'; import { LibraryEffects } from './effects/library.effects'; import { LibraryService } from './services/library.service'; @@ -85,6 +86,9 @@ import { DuplicatePagesEffects } from 'app/library/effects/duplicate-pages.effec import { DuplicatePageGridItemComponent } from './components/duplicate-page-grid-item/duplicate-page-grid-item.component'; import { DuplicatesPageToolbarComponent } from './components/duplicates-page-toolbar/duplicates-page-toolbar.component'; import { DuplicatePageListItemComponent } from './components/duplicate-page-list-item/duplicate-page-list-item.component'; +import { CollectionService } from 'app/library/services/collection.service'; +import { CollectionAdaptor } from 'app/library/adaptors/collection.adaptor'; +import { CollectionEffects } from 'app/library/effects/collection.effects'; @NgModule({ imports: [ @@ -112,10 +116,15 @@ import { DuplicatePageListItemComponent } from './components/duplicate-page-list fromDupes.DUPLICATE_PAGES_FEATURE_KEY, fromDupes.reducer ), + StoreModule.forFeature( + fromCollections.COLLECTION_FEATURE_KEY, + fromCollections.reducer + ), EffectsModule.forFeature([ LibraryEffects, ReadingListEffects, - DuplicatePagesEffects + DuplicatePagesEffects, + CollectionEffects ]), ContextMenuModule, CheckboxModule, @@ -168,7 +177,9 @@ import { DuplicatePageListItemComponent } from './components/duplicate-page-list DuplicatePagesAdaptors, ReadingListService, ReadingListAdaptor, - DuplicatePagesService + DuplicatePagesService, + CollectionService, + CollectionAdaptor ] }) export class LibraryModule { diff --git a/comixed-frontend/src/app/library/models/collection-entry.fixtures.ts b/comixed-frontend/src/app/library/models/collection-entry.fixtures.ts new file mode 100644 index 000000000..307b43bc7 --- /dev/null +++ b/comixed-frontend/src/app/library/models/collection-entry.fixtures.ts @@ -0,0 +1,44 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { CollectionEntry } from 'app/library/models/collection-entry'; + +export const COLLECTION_ENTRY_1: CollectionEntry = { + name: 'Collection 1', + count: 7 +}; + +export const COLLECTION_ENTRY_2: CollectionEntry = { + name: 'Collection 2', + count: 17 +}; + +export const COLLECTION_ENTRY_3: CollectionEntry = { + name: 'Collection 4', + count: 65 +}; + +export const COLLECTION_ENTRY_4: CollectionEntry = { + name: 'Collection 4', + count: 1 +}; + +export const COLLECTION_ENTRY_5: CollectionEntry = { + name: 'Collection 5', + count: 29 +}; diff --git a/comixed-frontend/src/app/library/models/collection-entry.ts b/comixed-frontend/src/app/library/models/collection-entry.ts new file mode 100644 index 000000000..017f90754 --- /dev/null +++ b/comixed-frontend/src/app/library/models/collection-entry.ts @@ -0,0 +1,22 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +export interface CollectionEntry { + name: string; + count: number; +} diff --git a/comixed-frontend/src/app/library/models/collection-type.enum.ts b/comixed-frontend/src/app/library/models/collection-type.enum.ts new file mode 100644 index 000000000..f0129da83 --- /dev/null +++ b/comixed-frontend/src/app/library/models/collection-type.enum.ts @@ -0,0 +1,26 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +export enum CollectionType { + PUBLISHERS = 'publisher', + SERIES = 'series', + CHARACTERS = 'characters', + TEAMS = 'teams', + LOCATIONS = 'locations', + STORIES = 'stories' +} diff --git a/comixed-frontend/src/app/library/models/net/get-collection-page-request.ts b/comixed-frontend/src/app/library/models/net/get-collection-page-request.ts new file mode 100644 index 000000000..1139822d2 --- /dev/null +++ b/comixed-frontend/src/app/library/models/net/get-collection-page-request.ts @@ -0,0 +1,26 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { CollectionType } from 'app/library/models/collection-type.enum'; + +export interface GetCollectionPageRequest { + page: number; + count: number; + sortField: string; + ascending: boolean; +} diff --git a/comixed-frontend/src/app/library/models/net/get-collection-page-response.ts b/comixed-frontend/src/app/library/models/net/get-collection-page-response.ts new file mode 100644 index 000000000..9de6e81da --- /dev/null +++ b/comixed-frontend/src/app/library/models/net/get-collection-page-response.ts @@ -0,0 +1,24 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Comic } from 'app/comics'; + +export interface GetCollectionPageResponse { + comics: Comic[]; + comicCount: number; +} diff --git a/comixed-frontend/src/app/library/reducers/collection.reducer.spec.ts b/comixed-frontend/src/app/library/reducers/collection.reducer.spec.ts new file mode 100644 index 000000000..950781532 --- /dev/null +++ b/comixed-frontend/src/app/library/reducers/collection.reducer.spec.ts @@ -0,0 +1,212 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { CollectionState, initialState, reducer } from './collection.reducer'; +import { + CollectionComicsReceived, + CollectionGetComics, + CollectionGetComicsFailed, + CollectionLoad, + CollectionLoadFailed, + CollectionReceived +} from 'app/library/actions/collection.actions'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 +} from 'app/library/models/collection-entry.fixtures'; +import { COMIC_1, COMIC_2, COMIC_3 } from 'app/comics/comics.fixtures'; + +describe('Collection Reducer', () => { + const COLLECTION_TYPE = CollectionType.PUBLISHERS; + const COLLECTION_ENTRIES = [ + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 + ]; + const COLLECTION_NAME = 'Fancy Publisher'; + const PAGE = 17; + const COUNT = 25; + const SORT_FIELD = 'addedDate'; + const ASCENDING = false; + const COMICS = [COMIC_1, COMIC_2, COMIC_3]; + const COMIC_COUNT = 3; + + let state: CollectionState; + + beforeEach(() => { + state = initialState; + }); + + describe('the initial state', () => { + beforeEach(() => { + state = reducer(state, {} as any); + }); + + it('clears the fetching entries flag', () => { + expect(state.fetchingEntries).toBeFalsy(); + }); + + it('has no default collection', () => { + expect(state.type).toBeNull(); + }); + + it('has no entries', () => { + expect(state.entries).toEqual([]); + }); + + it('has no selected entry', () => { + expect(state.selected).toBeNull(); + }); + + it('clears the fetching comics flag', () => { + expect(state.fetchingEntry).toBeFalsy(); + }); + + it('has page of 0', () => { + expect(state.page).toEqual(0); + }); + + it('has no comics', () => { + expect(state.comics).toEqual([]); + }); + + it('has a comic count of 0', () => { + expect(state.comicCount).toEqual(0); + }); + }); + + describe('getting entries for a collection', () => { + beforeEach(() => { + state = reducer( + { ...state, fetchingEntries: false, type: null }, + new CollectionLoad({ collectionType: COLLECTION_TYPE }) + ); + }); + + it('sets the fetching entries flag', () => { + expect(state.fetchingEntries).toBeTruthy(); + }); + + it('sets the collection type', () => { + expect(state.type).toEqual(COLLECTION_TYPE); + }); + }); + + describe('receiving entries for a collection', () => { + beforeEach(() => { + state = reducer( + { ...state, fetchingEntries: true, entries: [] }, + new CollectionReceived({ entries: COLLECTION_ENTRIES }) + ); + }); + + it('clears the fetching entries flag', () => { + expect(state.fetchingEntries).toBeFalsy(); + }); + + it('sets the list of entries', () => { + expect(state.entries).toEqual(COLLECTION_ENTRIES); + }); + }); + + describe('failure to get entries for a collection', () => { + beforeEach(() => { + state = reducer( + { ...state, fetchingEntries: true }, + new CollectionLoadFailed() + ); + }); + + it('clears the fetching entries flag', () => { + expect(state.fetchingEntries).toBeFalsy(); + }); + }); + + describe('get a single collection', () => { + beforeEach(() => { + state = reducer( + { ...state, fetchingEntry: false }, + new CollectionGetComics({ + collectionType: COLLECTION_TYPE, + name: COLLECTION_NAME, + page: PAGE, + count: COUNT, + sortField: SORT_FIELD, + ascending: ASCENDING + }) + ); + }); + + it('sets the fetching entry flag', () => { + expect(state.fetchingEntry).toBeTruthy(); + }); + + it('sets the current page', () => { + expect(state.page).toEqual(PAGE); + }); + }); + + describe('when a collection has been received', () => { + beforeEach(() => { + state = reducer( + { + ...state, + fetchingEntry: true, + selected: null, + comics: [], + page: PAGE - 3 + }, + new CollectionComicsReceived({ + comics: COMICS, + comicCount: COMIC_COUNT + }) + ); + }); + + it('clears the fetching entries flag', () => { + expect(state.fetchingEntry).toBeFalsy(); + }); + + it('updates the comics', () => { + expect(state.comics).toEqual(COMICS); + }); + + it('updates the comic count', () => { + expect(state.comicCount).toEqual(COMIC_COUNT); + }); + }); + + describe('failure to get a collection', () => { + beforeEach(() => { + state = reducer( + { ...state, fetchingEntry: true }, + new CollectionGetComicsFailed() + ); + }); + + it('clears the fetching entries flag', () => { + expect(state.fetchingEntry).toBeFalsy(); + }); + }); +}); diff --git a/comixed-frontend/src/app/library/reducers/collection.reducer.ts b/comixed-frontend/src/app/library/reducers/collection.reducer.ts new file mode 100644 index 000000000..1b0dc6736 --- /dev/null +++ b/comixed-frontend/src/app/library/reducers/collection.reducer.ts @@ -0,0 +1,86 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { + CollectionActions, + CollectionActionTypes +} from '../actions/collection.actions'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { CollectionEntry } from 'app/library/models/collection-entry'; +import { Comic } from 'app/comics'; + +export const COLLECTION_FEATURE_KEY = 'collection'; + +export interface CollectionState { + type: CollectionType; + fetchingEntries: boolean; + entries: CollectionEntry[]; + selected: CollectionEntry; + fetchingEntry: boolean; + page: number; + comics: Comic[]; + comicCount: number; +} + +export const initialState: CollectionState = { + type: null, + fetchingEntries: false, + entries: [], + selected: null, + fetchingEntry: false, + page: 0, + comics: [], + comicCount: 0 +}; + +export function reducer( + state = initialState, + action: CollectionActions +): CollectionState { + switch (action.type) { + case CollectionActionTypes.Load: + return { ...state, fetchingEntries: true, type: action.payload.collectionType }; + + case CollectionActionTypes.Received: + return { + ...state, + fetchingEntries: false, + entries: action.payload.entries + }; + + case CollectionActionTypes.LoadFailed: + return { ...state, fetchingEntries: false }; + + case CollectionActionTypes.GetComics: + return { ...state, fetchingEntry: true, page: action.payload.page }; + + case CollectionActionTypes.ComicsReceived: + return { + ...state, + fetchingEntry: false, + comics: action.payload.comics, + comicCount: action.payload.comicCount + }; + + case CollectionActionTypes.GetComicsFailed: + return { ...state, fetchingEntry: false }; + + default: + return state; + } +} diff --git a/comixed-frontend/src/app/library/services/collection.service.spec.ts b/comixed-frontend/src/app/library/services/collection.service.spec.ts new file mode 100644 index 000000000..0b977bb5e --- /dev/null +++ b/comixed-frontend/src/app/library/services/collection.service.spec.ts @@ -0,0 +1,124 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { TestBed } from '@angular/core/testing'; + +import { CollectionService } from './collection.service'; +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 +} from 'app/library/models/collection-entry.fixtures'; +import { COMIC_1, COMIC_2, COMIC_3 } from 'app/comics/models/comic.fixtures'; +import { interpolate } from 'app/app.functions'; +import { + GET_COLLECTION_ENTRIES_URL, + GET_PAGE_FOR_ENTRY_URL +} from 'app/library/library.constants'; +import { GetCollectionPageResponse } from 'app/library/models/net/get-collection-page-response'; +import { GetCollectionPageRequest } from 'app/library/models/net/get-collection-page-request'; + +describe('CollectionService', () => { + const COLLECTION_TYPE = CollectionType.CHARACTERS; + const COLLECTION_ENTRIES = [ + COLLECTION_ENTRY_1, + COLLECTION_ENTRY_2, + COLLECTION_ENTRY_3, + COLLECTION_ENTRY_4, + COLLECTION_ENTRY_5 + ]; + const COLLECTION_NAME = 'Fancy Publisher'; + const PAGE = 17; + const COUNT = 25; + const SORT_FIELD = 'addedDate'; + const ASCENDING = false; + const COMICS = [COMIC_1, COMIC_2, COMIC_3]; + const COMIC_COUNT = 3; + + let service: CollectionService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [CollectionService] + }); + + service = TestBed.get(CollectionService); + httpMock = TestBed.get(HttpTestingController); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('can get collection entries', () => { + service + .getEntries(COLLECTION_TYPE) + .subscribe(response => expect(response).toEqual(COLLECTION_ENTRIES)); + + const req = httpMock.expectOne( + interpolate(GET_COLLECTION_ENTRIES_URL, { type: COLLECTION_TYPE }) + ); + expect(req.request.method).toEqual('GET'); + req.flush(COLLECTION_ENTRIES); + }); + + it('can get a page of comics for a collection', () => { + service + .getPageForEntry( + COLLECTION_TYPE, + COLLECTION_NAME, + PAGE, + COUNT, + SORT_FIELD, + ASCENDING + ) + .subscribe(response => + expect(response).toEqual({ + comics: COMICS, + comicCount: COMIC_COUNT + } as GetCollectionPageResponse) + ); + + const req = httpMock.expectOne( + interpolate(GET_PAGE_FOR_ENTRY_URL, { + type: COLLECTION_TYPE, + name: COLLECTION_NAME + }) + ); + expect(req.request.method).toEqual('POST'); + expect(req.request.body).toEqual({ + page: PAGE, + count: COUNT, + sortField: SORT_FIELD, + ascending: ASCENDING + } as GetCollectionPageRequest); + req.flush({ + comics: COMICS, + comicCount: COMIC_COUNT + } as GetCollectionPageResponse); + }); +}); diff --git a/comixed-frontend/src/app/library/services/collection.service.ts b/comixed-frontend/src/app/library/services/collection.service.ts new file mode 100644 index 000000000..1e0bc6669 --- /dev/null +++ b/comixed-frontend/src/app/library/services/collection.service.ts @@ -0,0 +1,60 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2019, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Injectable } from '@angular/core'; +import { CollectionType } from 'app/library/models/collection-type.enum'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { interpolate } from 'app/app.functions'; +import { + GET_COLLECTION_ENTRIES_URL, + GET_PAGE_FOR_ENTRY_URL +} from 'app/library/library.constants'; +import { GetCollectionPageRequest } from 'app/library/models/net/get-collection-page-request'; + +@Injectable({ + providedIn: 'root' +}) +export class CollectionService { + constructor(private http: HttpClient) {} + + getEntries(type: CollectionType): Observable { + return this.http.get( + interpolate(GET_COLLECTION_ENTRIES_URL, { type: type }) + ); + } + + getPageForEntry( + type: CollectionType, + name: string, + page: number, + count: number, + sortField: string, + ascending: boolean + ): Observable { + return this.http.post( + interpolate(GET_PAGE_FOR_ENTRY_URL, { type: type, name: name }), + { + page: page, + count: count, + sortField: sortField, + ascending: ascending + } as GetCollectionPageRequest + ); + } +} diff --git a/comixed-frontend/src/assets/i18n/library-en.json b/comixed-frontend/src/assets/i18n/library-en.json index 1855834aa..765679b8b 100644 --- a/comixed-frontend/src/assets/i18n/library-en.json +++ b/comixed-frontend/src/assets/i18n/library-en.json @@ -268,5 +268,17 @@ "header": "Set Blocking State For Pages", "message": "Are you sure you want to {blocking, select, true{block} other{unblock}} the selected pages?" } + }, + "collection-effects": { + "get-entries": { + "error": { + "detail": "Failed to get all entries by {collectionType}." + } + }, + "get-page-for-entry": { + "error": { + "details": "Failed to load comics." + } + } } }