Misc Polishing (#413)
* Ensure that after we assign a role to a user, we show it immediately * Cached libraryType api as that is not going to change in a viewing session. Moved some components around to tighten bundles. * Cleaned up more TODOs * Refactored Configuration to use getter and setters so that the interface is a lot cleaner. Updated HashUtil to use JWT Secret instead of Machine name (as docker machine name is random each boot).
This commit is contained in:
parent
ef5b22b585
commit
b8165b311c
29 changed files with 408 additions and 307 deletions
|
|
@ -1,15 +0,0 @@
|
|||
<ng-container *ngIf="data !== undefined">
|
||||
<div class="card" style="width: 18rem;">
|
||||
<div class="overlay" (click)="handleClick()">
|
||||
<i class="fa {{icon}} card-img-top text-center" aria-hidden="true"></i>
|
||||
<div class="card-actions">
|
||||
<app-card-actionables [actions]="actions" [labelBy]="data.name" iconClass="fa-ellipsis-v" (actionHandler)="performAction($event)"></app-card-actionables>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body text-center" *ngIf="data.name.length > 0 || actions.length > 0">
|
||||
<span class="card-data.name" (click)="handleClick()">
|
||||
{{data.name}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
$primary-color: #cc7b19;
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
max-width: 160px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin-top: 5px;
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
max-width: 150px;
|
||||
height: 52px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
height: 160px;
|
||||
margin-top: 40% !important;
|
||||
margin: auto;
|
||||
font-size: 52px;
|
||||
|
||||
}
|
||||
|
||||
.overlay {
|
||||
height: 160px;
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
visibility: visible;
|
||||
|
||||
.overlay-item {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-item {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
position: absolute;
|
||||
top: 125px;
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
.dropdown-toggle:after {
|
||||
content: none !important;
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { Library } from 'src/app/_models/library';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
|
||||
import { ActionService } from 'src/app/_services/action.service';
|
||||
|
||||
// Represents a library type card.
|
||||
@Component({
|
||||
selector: 'app-library-card',
|
||||
templateUrl: './library-card.component.html',
|
||||
styleUrls: ['./library-card.component.scss']
|
||||
})
|
||||
export class LibraryCardComponent implements OnInit, OnChanges {
|
||||
@Input() data!: Library;
|
||||
@Output() clicked = new EventEmitter<Library>();
|
||||
|
||||
isAdmin = false;
|
||||
actions: ActionItem<Library>[] = [];
|
||||
icon = 'fa-book-open';
|
||||
|
||||
constructor(private accountService: AccountService, private router: Router,
|
||||
private actionFactoryService: ActionFactoryService, private actionService: ActionService) {
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: any) {
|
||||
if (this.data) {
|
||||
if (this.data.type === 0 || this.data.type === 1) {
|
||||
this.icon = 'fa-book-open';
|
||||
} else {
|
||||
this.icon = 'fa-book';
|
||||
}
|
||||
|
||||
this.actions = this.actionFactoryService.getLibraryActions(this.handleAction.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
handleAction(action: Action, library: Library) {
|
||||
switch (action) {
|
||||
case(Action.ScanLibrary):
|
||||
this.actionService.scanLibrary(library);
|
||||
break;
|
||||
case(Action.RefreshMetadata):
|
||||
this.actionService.refreshMetadata(library);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
performAction(action: ActionItem<Library>) {
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action.action, this.data);
|
||||
}
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.clicked.emit(this.data);
|
||||
this.router.navigate(['library', this.data?.id]);
|
||||
}
|
||||
|
||||
preventClick(event: any) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<div class="text-danger" *ngIf="errors.length > 0">
|
||||
<p>Errors:</p>
|
||||
<ul>
|
||||
<li *ngFor="let error of errors">{{error}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="register()">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" class="form-control" formControlName="username" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" class="form-control" formControlName="password" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-check" *ngIf="!firstTimeFlow">
|
||||
<input id="admin" type="checkbox" aria-label="Admin" class="form-check-input" formControlName="isAdmin">
|
||||
<label for="admin" class="form-check-label">Admin</label>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<button class="btn btn-secondary mr-2" type="button" (click)="cancel()">Cancel</button>
|
||||
<button class="btn btn-primary" type="submit">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register-member',
|
||||
templateUrl: './register-member.component.html',
|
||||
styleUrls: ['./register-member.component.scss']
|
||||
})
|
||||
export class RegisterMemberComponent implements OnInit {
|
||||
|
||||
@Input() firstTimeFlow = false;
|
||||
@Output() created = new EventEmitter<boolean>();
|
||||
|
||||
adminExists = false;
|
||||
registerForm: FormGroup = new FormGroup({
|
||||
username: new FormControl('', [Validators.required]),
|
||||
password: new FormControl('', [Validators.required]),
|
||||
isAdmin: new FormControl(false, [])
|
||||
});
|
||||
errors: string[] = [];
|
||||
|
||||
constructor(private accountService: AccountService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.firstTimeFlow) {
|
||||
this.registerForm.get('isAdmin')?.setValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
register() {
|
||||
this.accountService.register(this.registerForm.value).subscribe(resp => {
|
||||
this.created.emit(true);
|
||||
}, err => {
|
||||
this.errors = err;
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.created.emit(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<ng-container *ngIf="data !== undefined">
|
||||
<app-card-item [title]="data.name" [actions]="actions" [supressLibraryLink]="suppressLibraryLink" [imageUrl]="imageService.getSeriesCoverImage(data.id)" [entity]="data" [total]="data.pages" [read]="data.pagesRead" (clicked)="handleClick()"></app-card-item>
|
||||
</ng-container>
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { EditSeriesModalComponent } from 'src/app/_modals/edit-series-modal/edit-series-modal.component';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
import { ImageService } from 'src/app/_services/image.service';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { ActionFactoryService, Action, ActionItem } from 'src/app/_services/action-factory.service';
|
||||
import { SeriesService } from 'src/app/_services/series.service';
|
||||
import { ConfirmService } from '../confirm.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-series-card',
|
||||
templateUrl: './series-card.component.html',
|
||||
styleUrls: ['./series-card.component.scss']
|
||||
})
|
||||
export class SeriesCardComponent implements OnInit, OnChanges {
|
||||
@Input() data: Series | undefined;
|
||||
@Input() libraryId = 0;
|
||||
@Input() suppressLibraryLink = false;
|
||||
@Output() clicked = new EventEmitter<Series>();
|
||||
@Output() reload = new EventEmitter<boolean>();
|
||||
@Output() dataChanged = new EventEmitter<Series>();
|
||||
|
||||
isAdmin = false;
|
||||
actions: ActionItem<Series>[] = [];
|
||||
|
||||
constructor(private accountService: AccountService, private router: Router,
|
||||
private seriesService: SeriesService, private toastr: ToastrService,
|
||||
private libraryService: LibraryService, private modalService: NgbModal,
|
||||
private confirmService: ConfirmService, public imageService: ImageService,
|
||||
private actionFactoryService: ActionFactoryService) {
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: any) {
|
||||
if (this.data) {
|
||||
this.actions = this.actionFactoryService.getSeriesActions((action: Action, series: Series) => this.handleSeriesActionCallback(action, series));
|
||||
}
|
||||
}
|
||||
|
||||
handleSeriesActionCallback(action: Action, series: Series) {
|
||||
switch (action) {
|
||||
case(Action.MarkAsRead):
|
||||
this.markAsRead(series);
|
||||
break;
|
||||
case(Action.MarkAsUnread):
|
||||
this.markAsUnread(series);
|
||||
break;
|
||||
case(Action.ScanLibrary):
|
||||
this.scanLibrary(series);
|
||||
break;
|
||||
case(Action.RefreshMetadata):
|
||||
this.refreshMetdata(series);
|
||||
break;
|
||||
case(Action.Delete):
|
||||
this.deleteSeries(series);
|
||||
break;
|
||||
case(Action.Edit):
|
||||
this.openEditModal(series);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
openEditModal(data: Series) {
|
||||
const modalRef = this.modalService.open(EditSeriesModalComponent, { size: 'lg', scrollable: true });
|
||||
modalRef.componentInstance.series = data;
|
||||
modalRef.closed.subscribe((closeResult: {success: boolean, series: Series}) => {
|
||||
window.scrollTo(0, 0);
|
||||
if (closeResult.success) {
|
||||
this.seriesService.getSeries(data.id).subscribe(series => {
|
||||
this.data = series;
|
||||
this.reload.emit(true);
|
||||
this.dataChanged.emit(series);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refreshMetdata(series: Series) {
|
||||
this.seriesService.refreshMetadata(series).subscribe((res: any) => {
|
||||
this.toastr.success('Refresh started for ' + series.name);
|
||||
});
|
||||
}
|
||||
|
||||
scanLibrary(series: Series) {
|
||||
this.seriesService.scan(series.libraryId, series.id).subscribe((res: any) => {
|
||||
this.toastr.success('Scan started for ' + series.name);
|
||||
});
|
||||
}
|
||||
|
||||
async deleteSeries(series: Series) {
|
||||
if (!await this.confirmService.confirm('Are you sure you want to delete this series? It will not modify files on disk.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.seriesService.delete(series.id).subscribe((res: boolean) => {
|
||||
if (res) {
|
||||
this.toastr.success('Series deleted');
|
||||
this.reload.emit(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
markAsUnread(series: Series) {
|
||||
this.seriesService.markUnread(series.id).subscribe(res => {
|
||||
this.toastr.success(series.name + ' is now unread');
|
||||
series.pagesRead = 0;
|
||||
if (this.data) {
|
||||
this.data.pagesRead = 0;
|
||||
}
|
||||
|
||||
this.dataChanged.emit(series);
|
||||
});
|
||||
}
|
||||
|
||||
markAsRead(series: Series) {
|
||||
this.seriesService.markRead(series.id).subscribe(res => {
|
||||
this.toastr.success(series.name + ' is now read');
|
||||
series.pagesRead = series.pages;
|
||||
if (this.data) {
|
||||
this.data.pagesRead = series.pages;
|
||||
}
|
||||
this.dataChanged.emit(series);
|
||||
});
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.clicked.emit(this.data);
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.data?.id]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,14 +3,12 @@ import { CommonModule } from '@angular/common';
|
|||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { CardItemComponent } from './card-item/card-item.component';
|
||||
import { NgbCollapseModule, NgbDropdownModule, NgbPaginationModule, NgbProgressbarModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { LibraryCardComponent } from './library-card/library-card.component';
|
||||
import { SeriesCardComponent } from './series-card/series-card.component';
|
||||
import { CardDetailsModalComponent } from './_modals/card-details-modal/card-details-modal.component';
|
||||
import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component';
|
||||
import { SafeHtmlPipe } from './safe-html.pipe';
|
||||
import { LazyLoadImageModule } from 'ng-lazyload-image';
|
||||
import { CardActionablesComponent } from './card-item/card-actionables/card-actionables.component';
|
||||
import { RegisterMemberComponent } from './register-member/register-member.component';
|
||||
import { RegisterMemberComponent } from '../register-member/register-member.component';
|
||||
import { ReadMoreComponent } from './read-more/read-more.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { DrawerComponent } from './drawer/drawer.component';
|
||||
|
|
@ -24,8 +22,6 @@ import { A11yClickDirective } from './a11y-click.directive';
|
|||
declarations: [
|
||||
RegisterMemberComponent,
|
||||
CardItemComponent,
|
||||
LibraryCardComponent,
|
||||
SeriesCardComponent,
|
||||
CardDetailsModalComponent,
|
||||
ConfirmDialogComponent,
|
||||
SafeHtmlPipe,
|
||||
|
|
@ -49,10 +45,8 @@ import { A11yClickDirective } from './a11y-click.directive';
|
|||
NgbPaginationModule // CardDetailLayoutComponent
|
||||
],
|
||||
exports: [
|
||||
RegisterMemberComponent, // TODO: Move this out and put in normal app
|
||||
RegisterMemberComponent,
|
||||
CardItemComponent,
|
||||
LibraryCardComponent, // TODO: Move this out and put in normal app
|
||||
SeriesCardComponent, // TODO: Move this out and put in normal app
|
||||
SafeHtmlPipe,
|
||||
CardActionablesComponent,
|
||||
ReadMoreComponent,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue