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:
parent
fe5ec2f032
commit
1816b6c68d
20 changed files with 235 additions and 31 deletions
|
|
@ -1,6 +1,7 @@
|
|||
export interface Volume {
|
||||
id: number;
|
||||
number: string;
|
||||
number: number;
|
||||
name: string;
|
||||
files: Array<string>;
|
||||
coverImage: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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">×</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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: []
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<span class="card-title" (click)="handleClick()">
|
||||
{{title}}
|
||||
</span>
|
||||
<div class="pull-right">
|
||||
<ng-container *ngIf="actions.length > 0">
|
||||
<div class="col">
|
||||
<div ngbDropdown class="d-inline-block">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
3
src/app/shared/series-card/series-card.component.html
Normal file
3
src/app/shared/series-card/series-card.component.html
Normal 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>
|
||||
0
src/app/shared/series-card/series-card.component.scss
Normal file
0
src/app/shared/series-card/series-card.component.scss
Normal file
76
src/app/shared/series-card/series-card.component.ts
Normal file
76
src/app/shared/series-card/series-card.component.ts
Normal 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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 { }
|
||||
|
|
|
|||
BIN
src/assets/images/mock-cover.jpg
Normal file
BIN
src/assets/images/mock-cover.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Loading…
Add table
Add a link
Reference in a new issue