Skip to content

Commit

Permalink
Added scraping interactions to the comic edit tab [#559]
Browse files Browse the repository at this point in the history
 * Added components to build out the interface.
 * Moved cx-spacer to the global stylesheet.
  • Loading branch information
mcpierce committed Dec 25, 2020
1 parent 62be5c4 commit b3db577
Show file tree
Hide file tree
Showing 39 changed files with 1,352 additions and 87 deletions.
2 changes: 1 addition & 1 deletion comixed-web/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"aot": true,
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css",
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.scss"
],
"scripts": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-label class="spacer">ComiXed</mat-label>
<mat-label class="cx-spacer">ComiXed</mat-label>
<mat-label *ngIf="!!user">
{{ 'app.logged-in' | translate: {email: user.email} }}
<button mat-icon-button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
.spacer {
padding-left: 5px;
flex: 1 1 auto;
}
1 change: 1 addition & 0 deletions comixed-web/src/app/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '@app/core/reducers/busy.reducer';

export * from '@app/core/core.constants';
export { SortableListItem } from '@app/core/models/ui/sortable-list-item';
export { TokenService } from '@app/core/services/token.service';
export { AlertService } from '@app/core/services/alert.service';
export { ConfirmationService } from '@app/core/services/confirmation.service';
Expand Down
22 changes: 22 additions & 0 deletions comixed-web/src/app/core/models/ui/sortable-list-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2020, 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 <http://www.gnu.org/licenses>
*/

export interface SortableListItem<T> {
sortOrder: number;
item: T;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
class="cx-action-button cx-margin-left-5"
mat-flat-button
color="accent"
[disabled]="!comicForm.valid">
[disabled]="!comicForm.valid && hasApiKey"
(click)="onFetchScrapingVolumes()">
<mat-icon>cloud_circle</mat-icon>
<mat-label>{{"button.scrape"|translate}}</mat-label>
</button>
Expand All @@ -24,8 +25,51 @@
<mat-icon>undo</mat-icon>
<mat-label>{{"button.reset"|translate}}</mat-label>
</button>
<div class="cx-spacer"></div>
<mat-select id="maximum-records-select"
[(value)]="maximumRecords"
(selectionChange)="onMaximumRecordsChanged($event.value)">
<mat-option *ngFor="let option of maximumRecordsOptions"
[value]="option.value">{{option.label|translate}}</mat-option>
</mat-select>
<button id="skip-cache-button"
class="cx-action-button cx-margin-left-5"
mat-flat-button
color="accent"
[matTooltip]="'scraping.tooltip.skip-cache'|translate:{enabled:skipCache}"
(click)="onSkipCacheToggle()">
<mat-icon *ngIf="skipCache">block</mat-icon>
<mat-icon *ngIf="!skipCache">remove_circle_outline</mat-icon>
<mat-label>{{"button.skip-cache"|translate}}</mat-label>
</button>
</mat-toolbar>
<form [formGroup]="comicForm">
<mat-form-field class="cx-width-100">
<mat-label>{{"comic.label.api-key"|translate}}</mat-label>
<input id="api-key"
matInput
formControlName="apiKey"
[placeholder]="'comic.placeholder.api-key'|translate">
<button id="save-api-key-button"
class="cx-action-button cx-margin-left-5"
mat-flat-button
matSuffix
color="primary"
[disabled]="!hasApiKey"
(click)="onSaveApiKey()">
<mat-icon>save</mat-icon>
<mat-label>{{"button.save"|translate}}</mat-label>
</button>
<button id="reset-api-key-button"
class="cx-action-button cx-margin-left-5"
mat-flat-button
matSuffix
color="warn"
(click)="onResetApiKey()">
<mat-icon>undo</mat-icon>
<mat-label>{{"button.reset"|translate}}</mat-label>
</button>
</mat-form-field>
<mat-form-field class="cx-width-100">
<mat-label>{{"comic.label.publisher"|translate}}</mat-label>
<input matInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,21 @@ import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { saveUserPreference } from '@app/user/actions/user.actions';
import {
API_KEY_PREFERENCE,
MAXIMUM_RECORDS_PREFERENCE
} from '@app/library/library.constants';

describe('ComicEditComponent', () => {
const COMIC = COMIC_2;
const API_KEY = '1234567890ABCDEF';
const SKIP_CACHE = Math.random() > 0.5;
const MAXIMUM_RECORDS = 100;
const ISSUE_NUMBER = '27';

const initialState = {};

let component: ComicEditComponent;
Expand All @@ -54,15 +66,21 @@ describe('ComicEditComponent', () => {
MatFormFieldModule,
MatToolbarModule,
MatIconModule,
MatInputModule
MatInputModule,
MatSelectModule,
MatTooltipModule
],
providers: [provideMockStore({}), ConfirmationService]
}).compileComponents();

fixture = TestBed.createComponent(ComicEditComponent);
component = fixture.componentInstance;
component.apiKey = API_KEY;
component.maximumRecords = MAXIMUM_RECORDS;
component.skipCache = SKIP_CACHE;
component.comic = COMIC;
store = TestBed.inject(MockStore);
spyOn(store, 'dispatch');
confirmationService = TestBed.inject(ConfirmationService);
fixture.detectChanges();
}));
Expand Down Expand Up @@ -94,4 +112,77 @@ describe('ComicEditComponent', () => {
expect(confirmationService.confirm).toHaveBeenCalled();
});
});

it('sets the API key', () => {
expect(component.apiKey).toEqual(API_KEY);
});

describe('fetching the scraping volumes', () => {
beforeEach(() => {
spyOn(component.scrape, 'emit');
component.onFetchScrapingVolumes();
});

it('emits an event', () => {
expect(component.scrape.emit).toHaveBeenCalledWith({
apiKey: API_KEY,
series: COMIC.series,
volume: COMIC.volume,
issueNumber: COMIC.issueNumber,
maximumRecords: MAXIMUM_RECORDS,
skipCache: SKIP_CACHE
});
});
});

describe('the API key', () => {
describe('saving it', () => {
beforeEach(() => {
component.onSaveApiKey();
});

it('fires an action', () => {
expect(store.dispatch).toHaveBeenCalledWith(
saveUserPreference({ name: API_KEY_PREFERENCE, value: API_KEY })
);
});
});

describe('resetting it', () => {
beforeEach(() => {
component.comicForm.controls.apiKey.setValue(API_KEY.substr(1));
fixture.detectChanges();
component.onResetApiKey();
});

it('restores the original value', () => {
expect(component.comicForm.controls.apiKey.value).toEqual(API_KEY);
});
});
});

describe('toggling skipping the cache', () => {
beforeEach(() => {
component.onSkipCacheToggle();
});

it('flips the skip cache flag', () => {
expect(component.skipCache).toEqual(!SKIP_CACHE);
});
});

describe('setting the maximum records', () => {
beforeEach(() => {
component.onMaximumRecordsChanged(MAXIMUM_RECORDS);
});

it('fires an action', () => {
expect(store.dispatch).toHaveBeenCalledWith(
saveUserPreference({
name: MAXIMUM_RECORDS_PREFERENCE,
value: `${MAXIMUM_RECORDS}`
})
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,39 @@
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

import { Component, Input, OnInit } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Comic } from '@app/library';
import { Store } from '@ngrx/store';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { LoggerService } from '@angular-ru/logger';
import { ConfirmationService } from '@app/core';
import { TranslateService } from '@ngx-translate/core';
import { ScrapeEvent } from '@app/library/models/ui/scrape-event';
import { saveUserPreference } from '@app/user/actions/user.actions';
import {
API_KEY_PREFERENCE,
MAXIMUM_RECORDS_PREFERENCE,
SKIP_CACHE_PREFERENCE
} from '@app/library/library.constants';

@Component({
selector: 'cx-comic-edit',
templateUrl: './comic-edit.component.html',
styleUrls: ['./comic-edit.component.scss']
})
export class ComicEditComponent implements OnInit {
private _comic: Comic;
@Input() skipCache = false;
@Input() maximumRecords = 0;

@Output() scrape = new EventEmitter<ScrapeEvent>();

readonly maximumRecordsOptions = [
{ value: 0, label: 'scraping.label.all-records' },
{ value: 100, label: 'scraping.label.100-records' },
{ value: 1000, label: 'scraping.label.1000-records' }
];
comicForm: FormGroup;
scrapingMode = false;

constructor(
private logger: LoggerService,
Expand All @@ -42,14 +58,30 @@ export class ComicEditComponent implements OnInit {
private translateService: TranslateService
) {
this.comicForm = this.formBuilder.group({
apiKey: [''],
publisher: [''],
series: ['', [Validators.required]],
volume: [''],
issueNumber: ['', [Validators.required]]
});
}

ngOnInit(): void {}
private _apiKey = '';

get apiKey(): string {
return this._apiKey;
}

@Input() set apiKey(apiKey: string) {
this._apiKey = apiKey;
this.comicForm.controls.apiKey.setValue(apiKey);
}

private _comic: Comic;

get comic(): Comic {
return this._comic;
}

@Input() set comic(comic: Comic) {
this.logger.trace('Loading comic form:', comic);
Expand All @@ -61,14 +93,12 @@ export class ComicEditComponent implements OnInit {
this.comicForm.updateValueAndValidity();
}

get comic(): Comic {
return this._comic;
get hasApiKey(): boolean {
const apiKey = this.comicForm.controls.apiKey.value;
return !!apiKey && apiKey.length > 0;
}

private setInput(controlName: string, value: any): void {
this.logger.trace(`Setting form field: ${controlName}=${value}`);
this.comicForm.controls[controlName].setValue(value);
}
ngOnInit(): void {}

onUndoChanges(): void {
this.confirmationService.confirm({
Expand All @@ -77,7 +107,62 @@ export class ComicEditComponent implements OnInit {
confirm: () => {
this.logger.trace('Undoing changes');
this.comic = this._comic;
this.comicForm.markAsUntouched();
}
});
}

onFetchScrapingVolumes(): void {
this.logger.trace('Loading scraping volumes');
this.scrape.emit({
apiKey: this.comicForm.controls.apiKey.value,
series: this.comicForm.controls.series.value,
volume: this.comicForm.controls.volume.value,
issueNumber: this.comicForm.controls.issueNumber.value,
maximumRecords: this.maximumRecords,
skipCache: this.skipCache
});
}

onSaveApiKey(): void {
this.logger.trace('Saving the new API key');
this.store.dispatch(
saveUserPreference({
name: API_KEY_PREFERENCE,
value: this.comicForm.controls.apiKey.value
})
);
}

onResetApiKey(): void {
this.logger.trace('Resetting the API key changes');
this.comicForm.controls.apiKey.setValue(this.apiKey);
this.comicForm.controls.apiKey.markAsUntouched();
}

onSkipCacheToggle(): void {
this.logger.trace('Toggling skipping the cache');
this.skipCache = this.skipCache === false;
this.store.dispatch(
saveUserPreference({
name: SKIP_CACHE_PREFERENCE,
value: `${this.skipCache}`
})
);
}

onMaximumRecordsChanged(maximumRecords: number): void {
this.logger.trace('Changed maximum records');
this.store.dispatch(
saveUserPreference({
name: MAXIMUM_RECORDS_PREFERENCE,
value: `${maximumRecords}`
})
);
}

private setInput(controlName: string, value: any): void {
this.logger.trace(`Setting form field: ${controlName}=${value}`);
this.comicForm.controls[controlName].setValue(value);
}
}

0 comments on commit b3db577

Please sign in to comment.