diff --git a/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.html b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.html new file mode 100644 index 000000000..e9311389f --- /dev/null +++ b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.html @@ -0,0 +1,17 @@ +
+
+
+ + +
+
+ +
+
+
diff --git a/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.scss b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.scss new file mode 100644 index 000000000..7443f4431 --- /dev/null +++ b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.scss @@ -0,0 +1,3 @@ +p-checkbox { + padding-right: 5px; +} diff --git a/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.spec.ts b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.spec.ts new file mode 100644 index 000000000..ac617b0d9 --- /dev/null +++ b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.spec.ts @@ -0,0 +1,177 @@ +/* + * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConsolidateLibraryComponent } from './consolidate-library.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { CheckboxModule } from 'primeng/checkbox'; +import { TranslateModule } from '@ngx-translate/core'; +import { LoggerModule } from '@angular-ru/logger'; +import { ButtonModule } from 'primeng/button'; +import { AppState, LibraryAdaptor } from 'app/library'; +import { UserModule } from 'app/user/user.module'; +import { Store, StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { + LIBRARY_FEATURE_KEY, + reducer +} from 'app/library/reducers/library.reducer'; +import { LibraryEffects } from 'app/library/effects/library.effects'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Confirmation, ConfirmationService, MessageService } from 'primeng/api'; +import { ComicsModule } from 'app/comics/comics.module'; +import { AuthUserLoaded } from 'app/user/actions/authentication.actions'; +import { AuthenticationAdaptor, USER_ADMIN } from 'app/user'; +import { CONSOLIDATE_DELETE_PHYSICAL_FILES } from 'app/user/models/preferences.constants'; + +describe('ConsolidateLibraryComponent', () => { + let component: ConsolidateLibraryComponent; + let fixture: ComponentFixture; + let confirmationService: ConfirmationService; + let libraryAdaptor: LibraryAdaptor; + let authenticationAdaptor: AuthenticationAdaptor; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + UserModule, + ComicsModule, + HttpClientTestingModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + StoreModule.forRoot({}), + StoreModule.forFeature(LIBRARY_FEATURE_KEY, reducer), + EffectsModule.forRoot([]), + EffectsModule.forFeature([LibraryEffects]), + LoggerModule.forRoot(), + CheckboxModule, + ButtonModule + ], + declarations: [ConsolidateLibraryComponent], + providers: [LibraryAdaptor, MessageService, ConfirmationService] + }).compileComponents(); + + fixture = TestBed.createComponent(ConsolidateLibraryComponent); + component = fixture.componentInstance; + confirmationService = TestBed.get(ConfirmationService); + libraryAdaptor = TestBed.get(LibraryAdaptor); + authenticationAdaptor = TestBed.get(AuthenticationAdaptor); + store = TestBed.get(Store); + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('loading user preferences', () => { + it('sets the checkbox if the user did before', () => { + store.dispatch( + new AuthUserLoaded({ + user: { + ...USER_ADMIN, + preferences: [ + { name: CONSOLIDATE_DELETE_PHYSICAL_FILES, value: '1' } + ] + } + }) + ); + expect( + component.consolidationForm.controls['deletePhysicalFiles'].value + ).toBeTruthy(); + }); + + it('unsets the checkbox if the user did before', () => { + store.dispatch( + new AuthUserLoaded({ + user: { + ...USER_ADMIN, + preferences: [ + { name: CONSOLIDATE_DELETE_PHYSICAL_FILES, value: '0' } + ] + } + }) + ); + expect( + component.consolidationForm.controls['deletePhysicalFiles'].value + ).toBeFalsy(); + }); + }); + + describe('consolidating the library', () => { + beforeEach(() => { + spyOn( + confirmationService, + 'confirm' + ).and.callFake((confirm: Confirmation) => confirm.accept()); + spyOn(libraryAdaptor, 'consolidate'); + spyOn(authenticationAdaptor, 'setPreference'); + }); + + describe('and deletes the physical files', () => { + beforeEach(() => { + component.consolidationForm.controls['deletePhysicalFiles'].setValue( + true + ); + component.consolidateLibrary(); + }); + + it('prompts the user', () => { + expect(confirmationService.confirm).toHaveBeenCalled(); + }); + + it('calls the library adaptor', () => { + expect(libraryAdaptor.consolidate).toHaveBeenCalledWith(true); + }); + + it('saves the delete physical files flag as a preference', () => { + expect(authenticationAdaptor.setPreference).toHaveBeenCalledWith( + CONSOLIDATE_DELETE_PHYSICAL_FILES, + '1' + ); + }); + }); + + describe('and does not delete the physical file', () => { + beforeEach(() => { + component.consolidationForm.controls['deletePhysicalFiles'].setValue( + false + ); + component.consolidateLibrary(); + }); + + it('prompts the user', () => { + expect(confirmationService.confirm).toHaveBeenCalled(); + }); + + it('calls the library adaptor', () => { + expect(libraryAdaptor.consolidate).toHaveBeenCalledWith(false); + }); + + it('saves the delete physical files flag as a preference', () => { + expect(authenticationAdaptor.setPreference).toHaveBeenCalledWith( + CONSOLIDATE_DELETE_PHYSICAL_FILES, + '0' + ); + }); + }); + }); +}); diff --git a/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.ts b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.ts new file mode 100644 index 000000000..984a93af3 --- /dev/null +++ b/comixed-frontend/src/app/library/components/consolidate-library/consolidate-library.component.ts @@ -0,0 +1,93 @@ +/* + * 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 { Component, OnDestroy, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { LoggerService } from '@angular-ru/logger'; +import { LibraryAdaptor } from 'app/library'; +import { Subscription } from 'rxjs'; +import { CONSOLIDATE_DELETE_PHYSICAL_FILES } from 'app/user/models/preferences.constants'; +import { AuthenticationAdaptor } from 'app/user'; +import { ConfirmationService } from 'primeng/api'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'app-consolidate-library', + templateUrl: './consolidate-library.component.html', + styleUrls: ['./consolidate-library.component.scss'] +}) +export class ConsolidateLibraryComponent implements OnInit, OnDestroy { + consolidationForm: FormGroup; + consolidatingSubscription: Subscription; + consolidating = false; + userSubscription: Subscription; + user = null; + + constructor( + private logger: LoggerService, + private formBuilder: FormBuilder, + private libraryAdaptor: LibraryAdaptor, + private authenticationAdaptor: AuthenticationAdaptor, + private confirmationService: ConfirmationService, + private translateService: TranslateService + ) { + this.consolidationForm = this.formBuilder.group({ + deletePhysicalFiles: [''] + }); + this.consolidatingSubscription = this.libraryAdaptor.consolidating$.subscribe( + consolidating => (this.consolidating = consolidating) + ); + this.userSubscription = this.authenticationAdaptor.user$.subscribe(() => { + this.consolidationForm.controls['deletePhysicalFiles'].setValue( + this.authenticationAdaptor.getPreference( + CONSOLIDATE_DELETE_PHYSICAL_FILES + ) === '1' + ); + }); + } + + ngOnInit() {} + + ngOnDestroy() { + this.consolidatingSubscription.unsubscribe(); + } + + consolidateLibrary() { + this.confirmationService.confirm({ + header: this.translateService.instant( + 'consolidate-library.confirm.header' + ), + message: this.translateService.instant( + 'consolidate-library.confirm.message', + { + deletePhysicalFiles: this.consolidationForm.controls['deletePhysicalFiles'].value + } + ), + accept: () => { + const deletePhysicalFiles = this.consolidationForm.controls[ + 'deletePhysicalFiles' + ].value; + this.authenticationAdaptor.setPreference( + CONSOLIDATE_DELETE_PHYSICAL_FILES, + deletePhysicalFiles ? '1' : '0' + ); + this.libraryAdaptor.consolidate(deletePhysicalFiles); + } + }); + } +} diff --git a/comixed-frontend/src/app/library/library.module.ts b/comixed-frontend/src/app/library/library.module.ts index 8821f63cb..cdf573d19 100644 --- a/comixed-frontend/src/app/library/library.module.ts +++ b/comixed-frontend/src/app/library/library.module.ts @@ -75,6 +75,7 @@ import { ConvertComicsSettingsComponent } from './components/convert-comics-sett import * as fromPublisher from 'app/library/reducers/publisher.reducer'; import { PublisherEffects } from 'app/library/effects/publisher.effects'; import { PublisherAdaptor } from 'app/library/adaptors/publisher.adaptor'; +import { ConsolidateLibraryComponent } from './components/consolidate-library/consolidate-library.component'; @NgModule({ imports: [ @@ -138,7 +139,8 @@ import { PublisherAdaptor } from 'app/library/adaptors/publisher.adaptor'; DuplicatePageListItemComponent, CollectionDetailsPageComponent, CollectionPageComponent, - ConvertComicsSettingsComponent + ConvertComicsSettingsComponent, + ConsolidateLibraryComponent ], providers: [ LibraryService, diff --git a/comixed-frontend/src/app/user/models/preferences.constants.ts b/comixed-frontend/src/app/user/models/preferences.constants.ts index 31c0ec4b3..7142458c2 100644 --- a/comixed-frontend/src/app/user/models/preferences.constants.ts +++ b/comixed-frontend/src/app/user/models/preferences.constants.ts @@ -20,6 +20,8 @@ export const LIBRARY_SORT = 'library.sort-by'; export const LIBRARY_ROWS = 'library.rows'; export const LIBRARY_COVER_SIZE = 'library.cover-size'; export const LIBRARY_CURRENT_TAB = 'library.current-tab'; +export const CONSOLIDATE_DELETE_PHYSICAL_FILES = + 'library.consolidate.delete-physical-file'; export const IMPORT_SORT = 'import.sort-by'; export const IMPORT_ROWS = 'import.rows'; diff --git a/comixed-frontend/src/assets/i18n/library-en.json b/comixed-frontend/src/assets/i18n/library-en.json index 16d866fe7..2f772a33c 100644 --- a/comixed-frontend/src/assets/i18n/library-en.json +++ b/comixed-frontend/src/assets/i18n/library-en.json @@ -400,6 +400,18 @@ } } }, + "consolidate-library": { + "label": { + "delete-physical-files": "Delete The Physical Comic Files." + }, + "button": { + "start": "Start Consolidation" + }, + "confirm": { + "header": "Consolidate Library", + "message": "Are you sure you want to consolidate the library and {deletePhysicalFiles, select, true{delete} other{not delete}} the comic files?" + } + }, "breadcrumb": { "collections": { "root": "Collections",