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",