Added series card implementation. Added a mock cover to help visualize style changes. Implemented ability to create a new library without validation errors. Fixed a bug in directory picker that caused selected folders to not export the full path.

This commit is contained in:
Joseph Milazzo 2021-01-02 12:44:11 -06:00
parent fe5ec2f032
commit 1816b6c68d
20 changed files with 235 additions and 31 deletions

View file

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

View file

@ -37,4 +37,8 @@ export class LibraryService {
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);
}
}

View file

@ -84,7 +84,9 @@ export class DirectoryPickerComponent implements OnInit {
event.preventDefault();
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() {

View file

@ -1 +1,37 @@
<p>library-editor-modal works!</p>
<form [formGroup]="libraryForm" (ngSubmit)="submitLibrary()">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">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">
<label for="library-type">Type</label>
<select class="form-control" id="library-type" formControlName="type">
<option [value]="0">Manga</option>
</select>
</div>
<h4>Folders:</h4>
<button class="btn btn-primary" (click)="openDirectoryPicker()">Add Folder</button>
<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">Save</button>
</div>
</form>

View file

@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { LibraryService } from 'src/app/_services/library.service';
import { DirectoryPickerComponent, DirectoryPickerResult } from '../directory-picker/directory-picker.component';
@Component({
selector: 'app-library-editor-modal',
@ -7,9 +11,45 @@ import { Component, OnInit } from '@angular/core';
})
export class LibraryEditorModalComponent implements OnInit {
constructor() { }
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 {
}
submitLibrary() {
const model = this.libraryForm.value;
model.folders = this.selectedFolders;
console.log('Creating library with: ', model);
// this.libraryService.create(model).subscribe(() => {
// this.close(true);
// });
}
close(returnVal= false) {
this.modal.close(returnVal);
}
reset() {
}
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 { LibraryAccessModalComponent } from './_modals/library-access-modal/library-access-modal.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: [
CommonModule,
AdminRoutingModule,
ReactiveFormsModule,
FormsModule,
NgbNavModule,
SharedModule,
FormsModule
],
providers: []
})

View file

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

View file

@ -1,8 +1,7 @@
<h2>Libraries</h2>
<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-library-card [data]="library"></app-library-card>
<!-- <app-card-item [imageUrl]="library.coverImage" [title]="library.name" (clicked)="handleNavigation($event, library)" [actions]="actions" [entity]="library"></app-card-item> -->
</div>
</div>
</div>

View file

@ -25,6 +25,11 @@ export class LibraryComponent implements OnInit {
this.user = user;
this.libraryService.getLibrariesForMember(this.user.username).subscribe(libraries => {
this.libraries = 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';
}
});
});
}

View file

@ -11,7 +11,7 @@
<!-- TODO: Put SignalR notification button dropdown here. -->
<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 >
<button ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">Server Settings</button>
<button ngbDropdownItem (click)="logout()">Logout</button>

View file

@ -1,10 +1,10 @@
<div class="container" *ngIf="series !== undefined">
<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>
<button class="btn btn-primary">Read</button>
</div>
<div class="col-md-10">
<div class="col-md-8">
<h2>{{series.name | titlecase}}</h2>
<div class="row">
<ngb-rating></ngb-rating>
@ -21,7 +21,7 @@
<h4 class="mt-3">Volumes</h4>
<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>
</div>
</div>

View file

@ -3,16 +3,22 @@
<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">
<h5 class="card-title" (click)="handleClick()">{{title}}</h5>
<ng-container *ngIf="actions.length > 0">
<div class="col">
<div ngbDropdown class="d-inline-block">
<span class="card-title" (click)="handleClick()">
{{title}}
</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>
<div ngbDropdownMenu attr.aria-labelledby="actions-{{title}}">
<button ngbDropdownItem *ngFor="let action of actions" (click)="performAction($event, action)">{{action.title}}</button>
</div>
</div>
</div>
</ng-container>
</ng-container>
</div>
</div>
</div>

View file

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

View file

@ -15,7 +15,7 @@ export class CardItemComponent implements OnInit {
@Input() imageUrl = '';
@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.
@Output() clicked = new EventEmitter<string>();

View file

@ -40,11 +40,19 @@ export class LibraryCardComponent implements OnInit, OnChanges {
}
generateActions() {
this.actions = [];
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) => {
console.log('You tried to scan library: ' + data.name);
this.libraryService.scan(data?.id).subscribe((res: any) => {
this.toastr.success('Scan started for ' + data.name);
});
@ -52,6 +60,14 @@ export class LibraryCardComponent implements OnInit, OnChanges {
}
}
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/internal/operators/take';
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

@ -5,11 +5,17 @@ import { ReactiveFormsModule } from '@angular/forms';
import { CardItemComponent } from './card-item/card-item.component';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { LibraryCardComponent } from './library-card/library-card.component';
import { SeriesCardComponent } from './series-card/series-card.component';
@NgModule({
declarations: [RegisterMemberComponent, CardItemComponent, LibraryCardComponent],
declarations: [
RegisterMemberComponent,
CardItemComponent,
LibraryCardComponent,
SeriesCardComponent
],
imports: [
CommonModule,
ReactiveFormsModule,
@ -18,7 +24,8 @@ import { LibraryCardComponent } from './library-card/library-card.component';
exports: [
RegisterMemberComponent,
CardItemComponent,
LibraryCardComponent
LibraryCardComponent,
SeriesCardComponent
]
})
export class SharedModule { }

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB