Merge pull request #17 from Kareadita/feature/web-cleanup

UI Cleanup
This commit is contained in:
Joseph Milazzo 2021-01-03 17:06:03 -06:00 committed by GitHub
commit 0415ebb882
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 400 additions and 88 deletions

View file

@ -13,7 +13,7 @@ export class AuthGuard implements CanActivate {
constructor(private accountService: AccountService, private toastr: ToastrService) {} constructor(private accountService: AccountService, private toastr: ToastrService) {}
canActivate(): Observable<boolean> { canActivate(): Observable<boolean> {
// this automaticallys subs due to being router guard // TODO: on failure, can we bring them back to home to show login screen
return this.accountService.currentUser$.pipe( return this.accountService.currentUser$.pipe(
map((user: User) => { map((user: User) => {
if (user) { if (user) {

View file

@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { MemberService } from '../_services/member.service';
@Injectable({
providedIn: 'root'
})
export class LibraryAccessGuard implements CanActivate {
constructor(private memberService: MemberService) {}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const libraryId = parseInt(state.url.split('library/')[1], 10);
return this.memberService.hasLibraryAccess(libraryId);
}
}

View file

@ -1,6 +1,7 @@
export interface Volume { export interface Volume {
id: number; id: number;
number: string; number: number;
name: string;
files: Array<string>; files: Array<string>;
coverImage: string; coverImage: string;
} }

View file

@ -33,6 +33,20 @@ export class LibraryService {
return this.httpClient.post(this.baseUrl + 'library/update-for', {username, selectedLibraries}); return this.httpClient.post(this.baseUrl + 'library/update-for', {username, selectedLibraries});
} }
scan(libraryId: number) {
return this.httpClient.post(this.baseUrl + 'library/scan?libraryId=' + libraryId, {});
}
create(model: {name: string, type: number, folders: string[]}) {
return this.httpClient.post(this.baseUrl + 'library/create', model);
}
delete(libraryId: number) {
return this.httpClient.delete(this.baseUrl + 'library/delete?libraryId=' + libraryId, {});
}
update(model: {name: string, folders: string[], id: number}) {
return this.httpClient.post(this.baseUrl + 'library/update', model);
}
} }

View file

@ -24,4 +24,8 @@ export class MemberService {
return this.httpClient.delete(this.baseUrl + 'users/delete-user?username=' + username); return this.httpClient.delete(this.baseUrl + 'users/delete-user?username=' + username);
} }
hasLibraryAccess(libraryId: number) {
return this.httpClient.get<boolean>(this.baseUrl + 'users/has-library-access?libraryId=' + libraryId);
}
} }

View file

@ -15,7 +15,7 @@
<button *ngIf="routeStack.peek() !== undefined" (click)="goBack()" class="list-group-item list-group-item-action"><i class="fa fa-arrow-left mr-2"></i>Back</button> <button *ngIf="routeStack.peek() !== undefined" (click)="goBack()" class="list-group-item list-group-item-action"><i class="fa fa-arrow-left mr-2"></i>Back</button>
<button *ngFor="let folder of folders" class="list-group-item list-group-item-action" (click)="selectNode(folder)"> <button *ngFor="let folder of folders" class="list-group-item list-group-item-action" (click)="selectNode(folder)">
<span>{{getStem(folder)}}</span> <span>{{getStem(folder)}}</span>
<button class="btn btn-primary pull-right" (click)="shareFolder(folder, $event)">Share</button> <button type="button" class="btn btn-primary pull-right" (click)="shareFolder(folder, $event)">Share</button>
</button> </button>
<div class="text-center" *ngIf="folders.length === 0"> <div class="text-center" *ngIf="folders.length === 0">
There are no folders here There are no folders here

View file

@ -84,7 +84,9 @@ export class DirectoryPickerComponent implements OnInit {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.modal.close({success: true, folderPath: folderName}); const fullPath = (this.routeStack.items.join('\\') + '\\' + folderName).replace('\\\\', '\\');
this.modal.close({success: true, folderPath: fullPath});
} }
close() { close() {

View file

@ -1 +1,37 @@
<p>library-editor-modal works!</p>
<form [formGroup]="libraryForm">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{this.library !== undefined ? 'Edit' : 'New'}} Library</h4>
<button type="button" class="close" aria-label="Close" (click)="close()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="library-name">Name</label>
<input id="library-name" class="form-control" formControlName="name" type="text">
</div>
<div class="form-group" *ngIf="this.library === undefined">
<label for="library-type">Type</label>
<select class="form-control" id="library-type" formControlName="type">
<option [value]="0">Manga</option>
</select>
</div>
<h4>Folders <button type="button" class="btn pull-right" (click)="openDirectoryPicker()"><i class="fa fa-plus"></i></button></h4>
<div class="list-group">
<li class="list-group-item" *ngFor="let folder of selectedFolders; let i = index">
{{folder}}
<!-- TODO: Implement ability to remove folders added-->
</li>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="reset()">Reset</button>
<button type="button" class="btn btn-secondary" (click)="close()">Cancel</button>
<button type="submit" class="btn btn-primary" (click)="submitLibrary()">Save</button>
</div>
</form>

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LibraryEditorModalComponent } from './library-editor-modal.component';
describe('LibraryEditorModalComponent', () => {
let component: LibraryEditorModalComponent;
let fixture: ComponentFixture<LibraryEditorModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LibraryEditorModalComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LibraryEditorModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,15 +1,77 @@
import { Component, OnInit } from '@angular/core'; import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Library } from 'src/app/_models/library';
import { LibraryService } from 'src/app/_services/library.service';
import { DirectoryPickerComponent, DirectoryPickerResult } from '../directory-picker/directory-picker.component';
@Component({ @Component({
selector: 'app-library-editor-modal', selector: 'app-library-editor-modal',
templateUrl: './library-editor-modal.component.html', templateUrl: './library-editor-modal.component.html',
styleUrls: ['./library-editor-modal.component.scss'] styleUrls: ['./library-editor-modal.component.scss']
}) })
export class LibraryEditorModalComponent implements OnInit { export class LibraryEditorModalComponent implements OnInit, OnChanges {
constructor() { } @Input() library: Library | undefined = undefined;
libraryForm: FormGroup = new FormGroup({
name: new FormControl('', [Validators.required]),
type: new FormControl(0, [Validators.required])
});
selectedFolders: string[] = [];
constructor(private modalService: NgbModal, private libraryService: LibraryService, public modal: NgbActiveModal) { }
ngOnInit(): void { ngOnInit(): void {
this.setValues();
} }
ngOnChanges() {
console.log('Library: ', this.library);
}
submitLibrary() {
const model = this.libraryForm.value;
model.folders = this.selectedFolders;
if (this.library !== undefined) {
model.id = this.library.id;
this.libraryService.update(model).subscribe(() => {
this.close(true);
});
} else {
this.libraryService.create(model).subscribe(() => {
this.close(true);
});
}
}
close(returnVal= false) {
this.modal.close(returnVal);
}
reset() {
this.setValues();
}
setValues() {
if (this.library !== undefined) {
this.libraryForm.get('name')?.setValue(this.library.name);
this.libraryForm.get('type')?.setValue(this.library.type);
this.selectedFolders = this.library.folders;
}
}
openDirectoryPicker() {
const modalRef = this.modalService.open(DirectoryPickerComponent);
modalRef.closed.subscribe((closeResult: DirectoryPickerResult) => {
if (closeResult.success) {
this.selectedFolders.push(closeResult.folderPath);
}
});
}
} }

View file

@ -9,7 +9,7 @@ import { LibraryEditorModalComponent } from './_modals/library-editor-modal/libr
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { LibraryAccessModalComponent } from './_modals/library-access-modal/library-access-modal.component'; import { LibraryAccessModalComponent } from './_modals/library-access-modal/library-access-modal.component';
import { DirectoryPickerComponent } from './_modals/directory-picker/directory-picker.component'; import { DirectoryPickerComponent } from './_modals/directory-picker/directory-picker.component';
import { FormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -25,9 +25,10 @@ import { FormsModule } from '@angular/forms';
imports: [ imports: [
CommonModule, CommonModule,
AdminRoutingModule, AdminRoutingModule,
ReactiveFormsModule,
FormsModule,
NgbNavModule, NgbNavModule,
SharedModule, SharedModule,
FormsModule
], ],
providers: [] providers: []
}) })

View file

@ -10,8 +10,8 @@
<h4> <h4>
{{library.name | titlecase}} {{library.name | titlecase}}
<div class="pull-right"> <div class="pull-right">
<button class="btn btn-danger mr-2 btn-sm"><i class="fa fa-trash" title="Delete {{library.name | titlecase}}"></i></button> <button class="btn btn-danger mr-2 btn-sm" (click)="deleteLibrary(library)"><i class="fa fa-trash" title="Delete {{library.name | titlecase}}"></i></button>
<button class="btn btn-primary btn-sm" ><i class="fa fa-pencil" title="Edit {{library.name | titlecase}}"></i></button> <button class="btn btn-primary btn-sm" (click)="editLibrary(library)"><i class="fa fa-pencil" title="Edit {{library.name | titlecase}}"></i></button>
</div> </div>
</h4> </h4>
<!-- <div>Last Active: {{member.lastActive | date}}</div> <!-- <div>Last Active: {{member.lastActive | date}}</div>
@ -24,5 +24,3 @@
<app-library-editor-modal></app-library-editor-modal> <app-library-editor-modal></app-library-editor-modal>
</ng-template> </ng-template>
</div> </div>
<button class="btn btn-primary" (click)="addFolder('')">Add Folder</button>

View file

@ -19,25 +19,42 @@ export class ManageLibraryComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.getLibraries();
}
getLibraries() {
this.libraryService.getLibraries().subscribe(libraries => { this.libraryService.getLibraries().subscribe(libraries => {
this.libraries = libraries; this.libraries = libraries;
}); });
}
editLibrary(library: Library) {
const modalRef = this.modalService.open(LibraryEditorModalComponent);
console.log('component instance: ', modalRef.componentInstance);
modalRef.componentInstance.library = library;
modalRef.closed.subscribe(refresh => {
if (refresh) {
this.getLibraries();
}
});
} }
addLibrary() { addLibrary() {
const modalRef = this.modalService.open(LibraryEditorModalComponent); const modalRef = this.modalService.open(LibraryEditorModalComponent);
} modalRef.closed.subscribe(refresh => {
if (refresh) {
addFolder(library: string) { this.getLibraries();
const modalRef = this.modalService.open(DirectoryPickerComponent);
modalRef.closed.subscribe((closeResult: DirectoryPickerResult) => {
console.log('Closed Result', closeResult);
if (closeResult.success) {
console.log('Add folder path to Library');
} }
}); });
}
deleteLibrary(library: Library) {
if (confirm('Are you sure you want to delete this library? You cannot undo this action.')) {
this.libraryService.delete(library.id).subscribe(() => {
this.getLibraries();
});
}
} }
} }

View file

@ -4,6 +4,9 @@ import { HomeComponent } from './home/home.component';
import { LibraryDetailComponent } from './library-detail/library-detail.component'; import { LibraryDetailComponent } from './library-detail/library-detail.component';
import { LibraryComponent } from './library/library.component'; import { LibraryComponent } from './library/library.component';
import { SeriesDetailComponent } from './series-detail/series-detail.component'; import { SeriesDetailComponent } from './series-detail/series-detail.component';
import { AuthGuard } from './_guards/auth.guard';
import { LibraryAccessGuard } from './_guards/library-access.guard';
const routes: Routes = [ const routes: Routes = [
{path: '', component: HomeComponent}, {path: '', component: HomeComponent},
@ -12,8 +15,15 @@ const routes: Routes = [
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}, },
{path: 'library', component: LibraryComponent}, {path: 'library', component: LibraryComponent},
{path: 'library/:id', component: LibraryDetailComponent}, // NOTE: Should I put a guard up to prevent unauthorized access to libraries and series? {
{path: 'series/:id', component: SeriesDetailComponent}, path: '',
runGuardsAndResolvers: 'always',
canActivate: [AuthGuard, LibraryAccessGuard],
children: [
{path: 'library/:id', component: LibraryDetailComponent},
{path: 'library/:id/series/:id', component: SeriesDetailComponent},
]
},
{path: '**', component: HomeComponent, pathMatch: 'full'} {path: '**', component: HomeComponent, pathMatch: 'full'}
]; ];

View file

@ -1,12 +1,12 @@
<div class="container"> <div class="container">
<h2>Title (Manga/Recently Added)</h2> <h2>Title (Manga/Recently Added)</h2>
<div class="row"> <div class="row">
<div class="col-md-2" *ngFor="let manga of series"> <div class="col-sm-auto" *ngFor="let manga of series">
<app-card-item [title]="manga.name" (clicked)="seriesClicked(manga)"></app-card-item> <app-series-card [data]="manga" [libraryId]="libraryId"></app-series-card>
</div> </div>
</div> </div>
<ng-container *ngIf="series.length === 0"> <ng-container *ngIf="series.length === 0">
<!-- Put a cricket here --> <!-- TODO: Put a loader here -->
Nothing here.... Nothing here....
</ng-container> </ng-container>
</div> </div>

View file

@ -32,7 +32,7 @@ export class LibraryDetailComponent implements OnInit {
} }
seriesClicked(series: Series) { seriesClicked(series: Series) {
this.router.navigateByUrl('/series/' + series.id); this.router.navigate(['library', this.libraryId, 'series', series.id]);
} }
} }

View file

@ -1,7 +1,7 @@
<h2>Libraries</h2> <h2>Libraries</h2>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-4 col-lg-2" *ngFor="let library of libraries"> <div class="col-sm-auto" *ngFor="let library of libraries">
<app-card-item [imageUrl]="library.coverImage" [title]="library.name" (clicked)="handleNavigation($event, library)" [actions]="actions" [entity]="library"></app-card-item> <app-library-card [data]="library"></app-library-card>
</div> </div>
</div> </div>

View file

@ -17,29 +17,21 @@ export class LibraryComponent implements OnInit {
user: User | undefined; user: User | undefined;
libraries: Library[] = []; libraries: Library[] = [];
actions: CardItemAction[] = [];
constructor(public accountService: AccountService, private libraryService: LibraryService, private router: Router) { } constructor(public accountService: AccountService, private libraryService: LibraryService, private router: Router) { }
ngOnInit(): void { ngOnInit(): void {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
this.user = user; this.user = user;
if (this.accountService.hasAdminRole(user)) {
this.actions = [
{title: 'Scan Library', callback: (data: Library) => {
console.log('You tried to scan library: ' + data.name);
}}
];
}
this.libraryService.getLibrariesForMember(this.user.username).subscribe(libraries => { this.libraryService.getLibrariesForMember(this.user.username).subscribe(libraries => {
this.libraries = libraries; this.libraries = libraries;
console.log('Libraries: ', this.libraries); if (this.libraries.length > 0) {
// TODO: Remove this debug code
console.warn('Warning, debug code is being used!');
this.libraries[0].coverImage = '/assets/images/mock-cover.jpg';
}
}); });
}); });
} }
handleNavigation(event: any, library: Library) {
this.router.navigateByUrl('/library/' + library.id);
}
} }

View file

@ -11,7 +11,7 @@
<!-- TODO: Put SignalR notification button dropdown here. --> <!-- TODO: Put SignalR notification button dropdown here. -->
<div ngbDropdown class=" nav-item dropdown" *ngIf="(accountService.currentUser$ | async) as user" dropdown> <div ngbDropdown class=" nav-item dropdown" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
<button class="btn btn-outline-primary" ngbDropdownToggle>{{user.username | titlecase}}</button> <button class="btn btn-outline-secondary" ngbDropdownToggle>{{user.username | titlecase}}</button>
<div ngbDropdownMenu > <div ngbDropdownMenu >
<button ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">Server Settings</button> <button ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">Server Settings</button>
<button ngbDropdownItem (click)="logout()">Logout</button> <button ngbDropdownItem (click)="logout()">Logout</button>

View file

@ -1,10 +1,10 @@
<div class="container" *ngIf="series !== undefined"> <div class="container" *ngIf="series !== undefined">
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-4 col-mr-auto">
<app-card-item [imageUrl]="series.coverImage === null ? 'assets/images/image-placeholder.jpg' : series.coverImage"></app-card-item> <app-card-item [imageUrl]="series.coverImage === null ? 'assets/images/image-placeholder.jpg' : series.coverImage"></app-card-item>
<button class="btn btn-primary">Read</button> <button class="btn btn-primary">Read</button>
</div> </div>
<div class="col-md-10"> <div class="col-md-8">
<h2>{{series.name | titlecase}}</h2> <h2>{{series.name | titlecase}}</h2>
<div class="row"> <div class="row">
<ngb-rating></ngb-rating> <ngb-rating></ngb-rating>
@ -21,7 +21,7 @@
<h4 class="mt-3">Volumes</h4> <h4 class="mt-3">Volumes</h4>
<div class="row mt-3"> <div class="row mt-3">
<div class="col-md-2" *ngFor="let volume of volumes"> <div class="col-sm-auto" *ngFor="let volume of volumes">
<app-card-item [entity]="volume" [title]="'Volume ' + volume.number" (click)="openVolume(volume)"></app-card-item> <app-card-item [entity]="volume" [title]="'Volume ' + volume.number" (click)="openVolume(volume)"></app-card-item>
</div> </div>
</div> </div>

View file

@ -1,17 +1,24 @@
<div class="card" style="width: 18rem;"> <div class="card" style="width: 18rem;">
<!-- TODO: I need an anchor around this so you can open in new page easily -->
<img (click)="handleClick()" class="card-img-top" src="{{isNullOrEmpty(imageUrl) ? placeholderImage : imageUrl}}" alt="{{title}}"> <img (click)="handleClick()" class="card-img-top" src="{{isNullOrEmpty(imageUrl) ? placeholderImage : imageUrl}}" alt="{{title}}">
<div class="card-body text-center" *ngIf="title.length > 0 || actions.length > 0"> <div class="card-body text-center" *ngIf="title.length > 0 || actions.length > 0">
<h5 class="card-title" (click)="handleClick()">{{title}}</h5>
<ng-container *ngIf="actions.length > 0"> <span class="card-title" (click)="handleClick()">
<div class="col"> {{title}}
<div ngbDropdown class="d-inline-block"> </span>
<div class="pull-right">
<ng-container *ngIf="actions.length > 0">
<div ngbDropdown container="body" class="d-inline-block">
<button class="btn" id="actions-{{title}}" ngbDropdownToggle><i class="fa fa-ellipsis-v" aria-hidden="true"></i></button> <button class="btn" id="actions-{{title}}" ngbDropdownToggle><i class="fa fa-ellipsis-v" aria-hidden="true"></i></button>
<div ngbDropdownMenu attr.aria-labelledby="actions-{{title}}"> <div ngbDropdownMenu attr.aria-labelledby="actions-{{title}}">
<button ngbDropdownItem *ngFor="let action of actions" (click)="performAction($event, action)">{{action.title}}</button> <button ngbDropdownItem *ngFor="let action of actions" (click)="performAction($event, action)">{{action.title}}</button>
</div> </div>
</div> </div>
</div> </ng-container>
</ng-container> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,15 @@
.card { .card {
margin: 5px; margin: 10px;
max-width: 130px; // max-width: 130px;
max-height: 195px; // max-height: 195px;
max-width: 160px;
max-height: 320px;
// 370 x 210 roughly
}
.card-title {
margin-top: 5px;
} }
.card-body { .card-body {

View file

@ -15,10 +15,11 @@ export class CardItemComponent implements OnInit {
@Input() imageUrl = ''; @Input() imageUrl = '';
@Input() title = ''; @Input() title = '';
@Input() actions: CardItemAction[] = []; // TODO: Create a factory that generates actions based on if admin, etc. for each card type. @Input() actions: CardItemAction[] = [];
@Input() entity: any; // This is the entity we are representing. It will be returned if an action is executed. @Input() entity: any; // This is the entity we are representing. It will be returned if an action is executed.
@Output() clicked = new EventEmitter<string>(); @Output() clicked = new EventEmitter<string>();
placeholderImage = 'assets/images/image-placeholder.jpg'; placeholderImage = 'assets/images/image-placeholder.jpg';
constructor() { } constructor() { }

View file

@ -0,0 +1,3 @@
<ng-container *ngIf="data !== undefined">
<app-card-item [title]="data.name" [actions]="actions" [imageUrl]="data.coverImage" [entity]="data" (clicked)="handleClick()"></app-card-item>
</ng-container>

View file

@ -0,0 +1,76 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { Library } from 'src/app/_models/library';
import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service';
import { LibraryService } from 'src/app/_services/library.service';
import { CardItemAction } from '../card-item/card-item.component';
// Represents a library type card. Uses a app-card-item internally
@Component({
selector: 'app-library-card',
templateUrl: './library-card.component.html',
styleUrls: ['./library-card.component.scss']
})
export class LibraryCardComponent implements OnInit, OnChanges {
@Input() data: Library | undefined;
@Output() clicked = new EventEmitter<Library>();
isAdmin = false;
actions: CardItemAction[] = [];
constructor(private accountService: AccountService, private router: Router,
private libraryService: LibraryService, private toastr: ToastrService) {
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.generateActions();
}
}
generateActions() {
this.actions = [
{
title: 'Mark as Read',
callback: () => this.markAsRead
},
{
title: 'Mark as Unread',
callback: () => this.markAsUnread
}
];
if (this.isAdmin) {
this.actions.push({title: 'Scan Library', callback: (data: Library) => {
this.libraryService.scan(data?.id).subscribe((res: any) => {
this.toastr.success('Scan started for ' + data.name);
});
}});
}
}
markAsUnread(library: any) {
}
markAsRead(library: any) {
}
handleClick() {
this.clicked.emit(this.data);
this.router.navigate(['library', this.data?.id]);
}
}

View file

@ -0,0 +1,3 @@
<ng-container *ngIf="data !== undefined">
<app-card-item [title]="data.name" [actions]="actions" [imageUrl]="data.coverImage" [entity]="data" (clicked)="handleClick()"></app-card-item>
</ng-container>

View file

@ -0,0 +1,76 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { Series } from 'src/app/_models/series';
import { AccountService } from 'src/app/_services/account.service';
import { SeriesService } from 'src/app/_services/series.service';
import { CardItemAction } from '../card-item/card-item.component';
@Component({
selector: 'app-series-card',
templateUrl: './series-card.component.html',
styleUrls: ['./series-card.component.scss']
})
export class SeriesCardComponent implements OnInit {
@Input() data: Series | undefined;
@Input() libraryId = 0;
@Output() clicked = new EventEmitter<Series>();
isAdmin = false;
actions: CardItemAction[] = [];
constructor(private accountService: AccountService, private router: Router,
private seriesService: SeriesService, private toastr: ToastrService) {
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.generateActions();
}
}
generateActions() {
this.actions = [
{
title: 'Mark as Read',
callback: () => this.markAsRead
},
{
title: 'Mark as Unread',
callback: () => this.markAsUnread
}
];
if (this.isAdmin) {
// this.actions.push({title: 'Scan Library', callback: (data: Library) => {
// this.libraryService.scan(this.libraryId).subscribe((res: any) => {
// this.toastr.success('Scan started for ' + data.name);
// });
// }});
}
}
markAsUnread(series: any) {
}
markAsRead(series: any) {
}
handleClick() {
this.clicked.emit(this.data);
this.router.navigate(['library', this.libraryId, 'series', this.data?.id]);
}
}

View file

@ -4,11 +4,18 @@ import { RegisterMemberComponent } from './register-member/register-member.compo
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { CardItemComponent } from './card-item/card-item.component'; import { CardItemComponent } from './card-item/card-item.component';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { LibraryCardComponent } from './library-card/library-card.component';
import { SeriesCardComponent } from './series-card/series-card.component';
@NgModule({ @NgModule({
declarations: [RegisterMemberComponent, CardItemComponent], declarations: [
RegisterMemberComponent,
CardItemComponent,
LibraryCardComponent,
SeriesCardComponent
],
imports: [ imports: [
CommonModule, CommonModule,
ReactiveFormsModule, ReactiveFormsModule,
@ -16,7 +23,9 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
], ],
exports: [ exports: [
RegisterMemberComponent, RegisterMemberComponent,
CardItemComponent CardItemComponent,
LibraryCardComponent,
SeriesCardComponent
] ]
}) })
export class SharedModule { } export class SharedModule { }

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB