Drawers, Estimated Reading Time, Korean Parsing Support (#1297)

* Started building out idea around detail drawer. Need code from word count to continue

* Fixed the logic for caluclating time to read on comics

* Adding styles

* more styling fixes

* Cleaned up the styles a bit more so it's at least functional. Not sure on the feature, might abandon.

* Pulled Robbie's changes in and partially migrated them to the drawer.

* Add offset overrides for offcanvas so it takes our header into account

* Implemented a basic time left to finish the series (or at least what's in Kavita). Rough around the edges.

* Cleaned up the drawer code.

* Added Quick Catch ups to recommended page. Updated the timeout for scan tasks to ensure we don't run 2 at the same time.

* Quick catchups implemented

* Added preliminary support for Korean filename parsing. Reduced an array alloc that is called many thousands of times per scan.

* Fixing drawer overflow

* Fixed a calculation bug with average reading time.

* Small spacing changes to drawer

* Don't show estimated reading time if the user hasn't read anything

* Bump eventsource from 1.1.1 to 2.0.2 in /UI/Web

Bumps [eventsource](https://github.com/EventSource/eventsource) from 1.1.1 to 2.0.2.
- [Release notes](https://github.com/EventSource/eventsource/releases)
- [Changelog](https://github.com/EventSource/eventsource/blob/master/HISTORY.md)
- [Commits](https://github.com/EventSource/eventsource/compare/v1.1.1...v2.0.2)

---
updated-dependencies:
- dependency-name: eventsource
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Added image to series detail drawer

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Joseph Milazzo 2022-05-27 09:08:54 -05:00 committed by GitHub
parent d796bcdc0a
commit 63475722ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 883 additions and 144 deletions

View file

@ -0,0 +1,248 @@
<div class="offcanvas-header">
<h5 class="offcanvas-title">
<ng-container [ngSwitch]="libraryType">
<ng-container *ngSwitchCase="LibraryType.Comic">
<span class="modal-title" id="modal-basic-title">
<ng-container *ngIf="chapter.titleName != ''; else fullComicTitle">
{{chapter.titleName}}
</ng-container>
<ng-template #fullComicTitle>
{{parentName}} - {{data.number != 0 ? (isChapter ? 'Chapter ' : 'Volume ') + data.number : 'Special'}}
</ng-template>
</span>
</ng-container>
<ng-container *ngSwitchCase="LibraryType.Manga">
<span class="modal-title" id="modal-basic-title">
<ng-container *ngIf="chapter.titleName != ''; else fullMangaTitle">
{{chapter.titleName}}
</ng-container>
<ng-template #fullMangaTitle>
{{parentName}} - {{data.number != 0 ? (isChapter ? 'Issue #' : 'Volume ') + data.number : 'Special'}}
</ng-template>
</span>
</ng-container>
<ng-container *ngSwitchCase="LibraryType.Book">
<span class="modal-title" id="modal-basic-title">
{{chapter.titleName}}
</span>
</ng-container>
</ng-container>
</h5>
<button type="button" class="btn-close text-reset" aria-label="Close" (click)="activeOffcanvas.dismiss()"></button>
</div>
<div class="offcanvas-body pb-3">
<div class="d-flex">
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-pills" orientation="vertical" style="max-width: 135px;">
<li [ngbNavItem]="tabs[TabID.General]">
<a ngbNavLink>General</a>
<ng-template ngbNavContent>
<div class="container-fluid" style="overflow: auto">
<div class="row g-0">
<div class="d-none d-md-block col-md-2 col-lg-1">
<app-image class="me-2" width="74px" [imageUrl]="chapter.coverImage"></app-image>
</div>
<div *ngIf="summary$ | async as summary" class="col-md-10 col-lg-11">
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
</div>
</div>
<div class="row g-0 mt-4 mb-3">
<ng-container *ngIf="chapter.pages">
<div class="col-auto mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-file-lines" title="Pages">
{{chapter.pages}} Pages
</app-icon-and-title>
</div>
<div class="vr d-none d-lg-block m-2"></div>
</ng-container>
<ng-container *ngIf="chapterMetadata !== undefined && chapterMetadata.releaseDate">
<div class="col-auto mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-calendar" title="Release">
{{chapterMetadata.releaseDate | date:'shortDate'}}
</app-icon-and-title>
</div>
<div class="vr d-none d-lg-block m-2"></div>
</ng-container>
<ng-container *ngIf="chapter.files[0].format === MangaFormat.EPUB && chapterMetadata !== undefined && chapterMetadata.wordCount > 0 || chapter.files[0].format !== MangaFormat.EPUB">
<div class="col-auto mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-clock">
{{minHoursToRead}}{{maxHoursToRead !== minHoursToRead ? ('-' + maxHoursToRead) : ''}} Hour{{minHoursToRead > 1 ? 's' : ''}}
</app-icon-and-title>
</div>
</ng-container>
<ng-container *ngIf="chapter.files[0].format === MangaFormat.EPUB && chapterMetadata !== undefined && chapterMetadata.wordCount > 0">
<div class="vr d-none d-lg-block m-2"></div>
<div class="col-auto mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-book-open">
{{chapterMetadata.wordCount | compactNumber}} Words
</app-icon-and-title>
</div>
</ng-container>
<ng-container *ngIf="chapterMetadata !== undefined">
<ng-container *ngIf="ageRating !== '' && ageRating !== 'Unknown'">
<div class="vr d-none d-lg-block m-2"></div>
<div class="col-auto">
<app-icon-and-title [clickable]="false" fontClasses="fas fa-eye" title="Age Rating">
{{ageRating}}
</app-icon-and-title>
</div>
</ng-container>
</ng-container>
</div>
<!-- 2 rows to show some tags-->
<ng-container *ngIf="chapterMetadata !== undefined">
<div class="row g-0 mb-2">
<div class="col-md-6 col-sm-12">
<h6>Authors/Writers</h6>
<ng-container *ngIf="chapterMetadata.writers.length > 0; else noBadges">
<app-badge-expander [items]="chapterMetadata.writers">
<ng-template #badgeExpanderItem let-item let-position="idx">
<app-person-badge [person]="item"></app-person-badge>
</ng-template>
</app-badge-expander>
</ng-container>
</div>
<div class="col-md-6 col-sm-12">
<h6>Genres</h6>
<ng-container *ngIf="chapterMetadata.genres.length > 0; else noBadges">
<app-badge-expander [items]="chapterMetadata.genres">
<ng-template #badgeExpanderItem let-item let-position="idx">
<app-tag-badge>{{item.title}}</app-tag-badge>
</ng-template>
</app-badge-expander>
</ng-container>
</div>
</div>
<div class="row g-0 mb-2">
<div class="col-md-6 col-sm-12">
<h6>Publisher</h6>
<ng-container *ngIf="chapterMetadata.publishers.length > 0; else noBadges">
<app-badge-expander [items]="chapterMetadata.publishers">
<ng-template #badgeExpanderItem let-item let-position="idx">
<app-person-badge [person]="item"></app-person-badge>
</ng-template>
</app-badge-expander>
</ng-container>
</div>
<div class="col-md-6 col-sm-12">
<h6>Tags</h6>
<ng-container *ngIf="chapterMetadata.tags.length > 0; else noBadges">
<app-badge-expander [items]="chapterMetadata.tags">
<ng-template #badgeExpanderItem let-item let-position="idx">
<app-tag-badge>{{item.title}}</app-tag-badge>
</ng-template>
</app-badge-expander>
</ng-container>
</div>
</div>
<ng-template #noBadges>
Not defined
</ng-template>
</ng-container>
</div>
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.Metadata]">
<a ngbNavLink>{{tabs[TabID.Metadata].title}}</a>
<ng-template ngbNavContent>
<app-chapter-metadata-detail [chapter]="chapterMetadata"></app-chapter-metadata-detail>
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.Cover]">
<a ngbNavLink>{{tabs[TabID.Cover].title}}</a>
<ng-template ngbNavContent>
<app-cover-image-chooser [(imageUrls)]="imageUrls" (imageSelected)="updateSelectedIndex($event)" (selectedBase64Url)="updateSelectedImage($event)" [showReset]="chapter.coverImageLocked" (resetClicked)="handleReset()"></app-cover-image-chooser>
<div class="row g-0">
<button class="btn btn-primary flex-end mb-2" [disabled]="coverImageSaveLoading" (click)="saveCoverImage()">
<ng-container *ngIf="coverImageSaveLoading; else notSaving">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>
</ng-container>
<ng-template #notSaving>
Save
</ng-template>
</button>
</div>
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.Files]">
<a ngbNavLink>{{tabs[TabID.Files].title}}</a>
<ng-template ngbNavContent>
<div class="row g-0 mb-3">
<ng-container *ngIf="chapter.created && chapter.created !== '' && (chapter.created | date: 'shortDate') !== '1/1/01'">
<div class="col-auto">
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-file-import" title="Date Added">
Created: {{chapter.created | date:'short' || '-'}}
</app-icon-and-title>
</div>
</ng-container>
<ng-container>
<div class="vr d-none d-lg-block m-2"></div>
<div class="col-auto">
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-fingerprint" title="ID">
ID: {{data.id}}
</app-icon-and-title>
</div>
</ng-container>
</div>
<h4 *ngIf="!utilityService.isChapter(data)">{{utilityService.formatChapterName(libraryType) + 's'}}</h4>
<ul class="list-unstyled">
<li class="d-flex my-4" *ngFor="let chapter of chapters">
<a (click)="readChapter(chapter)" href="javascript:void(0);" title="Read {{utilityService.formatChapterName(libraryType, true, false)}} {{formatChapterNumber(chapter)}}">
<app-image class="me-2" width="74px" [imageUrl]="chapter.coverImage"></app-image>
</a>
<div class="flex-grow-1">
<h5 class="mt-0 mb-1">
<span >
<span>
<app-card-actionables (actionHandler)="performAction($event, chapter)" [actions]="chapterActions"
[labelBy]="utilityService.formatChapterName(libraryType, true, true) + formatChapterNumber(chapter)"></app-card-actionables>
<ng-container *ngIf="chapter.number !== '0'; else specialHeader">
{{utilityService.formatChapterName(libraryType, true, false) }} {{formatChapterNumber(chapter)}}
</ng-container>
</span>
<span class="badge bg-primary rounded-pill ms-1">
<span *ngIf="chapter.pagesRead > 0 && chapter.pagesRead < chapter.pages">{{chapter.pagesRead}} / {{chapter.pages}}</span>
<span *ngIf="chapter.pagesRead === 0">UNREAD</span>
<span *ngIf="chapter.pagesRead === chapter.pages">READ</span>
</span>
</span>
<ng-template #specialHeader>Files</ng-template>
</h5>
<ul class="list-group">
<li *ngFor="let file of chapter.files" class="list-group-item no-hover">
<span>{{file.filePath}}</span>
<div class="row g-0">
<div class="col">
Pages: {{file.pages}}
</div>
<div class="col" *ngIf="data.hasOwnProperty('created')">
Added: {{(data.created | date: 'short') || '-'}}
</div>
</div>
</li>
</ul>
</div>
</li>
</ul>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="tab-content {{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'mt-3' : 'ms-4 flex-fill'}}"></div>
</div>
</div>

View file

@ -0,0 +1,16 @@
.hide-if-empty:empty {
display: none !important;
}
.offcanvas-body {
overflow: auto;
}
.offcanvas-header {
padding: 1rem 1rem 0;
}
.tab-content {
overflow: auto;
height: calc(40vh - 62px); // drawer height - offcanvas heading height
}

View file

@ -0,0 +1,263 @@
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { NgbActiveOffcanvas } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { Observable, of, take } from 'rxjs';
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { ChapterMetadata } from 'src/app/_models/chapter-metadata';
import { LibraryType } from 'src/app/_models/library';
import { MangaFile } from 'src/app/_models/manga-file';
import { MangaFormat } from 'src/app/_models/manga-format';
import { PersonRole } from 'src/app/_models/person';
import { Volume } from 'src/app/_models/volume';
import { AccountService } from 'src/app/_services/account.service';
import { ActionItem, ActionFactoryService, Action } from 'src/app/_services/action-factory.service';
import { ActionService } from 'src/app/_services/action.service';
import { ImageService } from 'src/app/_services/image.service';
import { LibraryService } from 'src/app/_services/library.service';
import { MetadataService } from 'src/app/_services/metadata.service';
import { MAX_PAGES_PER_MINUTE, MAX_WORDS_PER_HOUR, MIN_PAGES_PER_MINUTE, MIN_WORDS_PER_HOUR, ReaderService } from 'src/app/_services/reader.service';
import { SeriesService } from 'src/app/_services/series.service';
import { UploadService } from 'src/app/_services/upload.service';
enum TabID {
General = 0,
Metadata = 1,
Cover = 2,
Files = 3
}
@Component({
selector: 'app-card-detail-drawer',
templateUrl: './card-detail-drawer.component.html',
styleUrls: ['./card-detail-drawer.component.scss']
})
export class CardDetailDrawerComponent implements OnInit {
@Input() parentName = '';
@Input() seriesId: number = 0;
@Input() libraryId: number = 0;
@Input() data!: Volume | Chapter;
/**
* If this is a volume, this will be first chapter for said volume.
*/
chapter!: Chapter;
isChapter = false;
chapters: Chapter[] = [];
/**
* If a cover image update occured.
*/
coverImageUpdate: boolean = false;
coverImageIndex: number = 0;
/**
* Url of the selected cover
*/
selectedCover: string = '';
coverImageLocked: boolean = false;
/**
* When the API is doing work
*/
coverImageSaveLoading: boolean = false;
imageUrls: Array<string> = [];
actions: ActionItem<any>[] = [];
chapterActions: ActionItem<Chapter>[] = [];
libraryType: LibraryType = LibraryType.Manga;
tabs = [{title: 'General', disabled: false}, {title: 'Metadata', disabled: false}, {title: 'Cover', disabled: false}, {title: 'Info', disabled: false}];
active = this.tabs[0];
chapterMetadata!: ChapterMetadata;
ageRating!: string;
summary$: Observable<string> = of('');
minHoursToRead: number = 1;
maxHoursToRead: number = 1;
get MangaFormat() {
return MangaFormat;
}
get Breakpoint() {
return Breakpoint;
}
get PersonRole() {
return PersonRole;
}
get LibraryType() {
return LibraryType;
}
get TabID() {
return TabID;
}
constructor(public utilityService: UtilityService,
public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService,
private accountService: AccountService, private actionFactoryService: ActionFactoryService,
private actionService: ActionService, private router: Router, private libraryService: LibraryService,
private seriesService: SeriesService, private readerService: ReaderService, public metadataService: MetadataService,
public activeOffcanvas: NgbActiveOffcanvas) { }
ngOnInit(): void {
this.isChapter = this.utilityService.isChapter(this.data);
this.chapter = this.utilityService.isChapter(this.data) ? (this.data as Chapter) : (this.data as Volume).chapters[0];
this.imageUrls.push(this.imageService.getChapterCoverImage(this.chapter.id));
this.seriesService.getChapterMetadata(this.chapter.id).subscribe(metadata => {
this.chapterMetadata = metadata;
this.metadataService.getAgeRating(this.chapterMetadata.ageRating).subscribe(ageRating => this.ageRating = ageRating);
if (this.chapter.files[0].format === MangaFormat.EPUB && this.chapterMetadata.wordCount > 0) {
this.minHoursToRead = parseInt(Math.round(this.chapterMetadata.wordCount / MAX_WORDS_PER_HOUR) + '', 10) || 1;
this.maxHoursToRead = parseInt(Math.round(this.chapterMetadata.wordCount / MIN_WORDS_PER_HOUR) + '', 10) || 1;
} else if (this.chapter.files[0].format !== MangaFormat.EPUB) {
this.minHoursToRead = parseInt(Math.round((this.chapter.pages / MIN_PAGES_PER_MINUTE) / 60) + '', 10) || 1;
this.maxHoursToRead = parseInt(Math.round((this.chapter.pages / MAX_PAGES_PER_MINUTE) / 60) + '', 10) || 1;
}
});
if (this.isChapter) {
this.summary$ = this.metadataService.getChapterSummary(this.data.id);
} else {
this.summary$ = this.metadataService.getChapterSummary(this.utilityService.asVolume(this.data).chapters[0].id);
}
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
if (!this.accountService.hasAdminRole(user)) {
this.tabs.find(s => s.title === 'Cover')!.disabled = true;
}
}
});
this.libraryService.getLibraryType(this.libraryId).subscribe(type => {
this.libraryType = type;
});
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this)).filter(item => item.action !== Action.Edit);
if (this.isChapter) {
this.chapters.push(this.data as Chapter);
} else if (!this.isChapter) {
this.chapters.push(...(this.data as Volume).chapters);
}
// TODO: Move this into the backend
this.chapters.sort(this.utilityService.sortChapters);
this.chapters.forEach(c => c.coverImage = this.imageService.getChapterCoverImage(c.id));
// Try to show an approximation of the reading order for files
var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
this.chapters.forEach((c: Chapter) => {
c.files.sort((a: MangaFile, b: MangaFile) => collator.compare(a.filePath, b.filePath));
});
}
close() {
this.activeOffcanvas.close({coverImageUpdate: this.coverImageUpdate});
}
formatChapterNumber(chapter: Chapter) {
if (chapter.number === '0') {
return '1';
}
return chapter.number;
}
performAction(action: ActionItem<any>, chapter: Chapter) {
if (typeof action.callback === 'function') {
action.callback(action.action, chapter);
}
}
updateSelectedIndex(index: number) {
this.coverImageIndex = index;
}
updateSelectedImage(url: string) {
this.selectedCover = url;
}
handleReset() {
this.coverImageLocked = false;
}
saveCoverImage() {
this.coverImageSaveLoading = true;
const selectedIndex = this.coverImageIndex || 0;
if (selectedIndex > 0) {
this.uploadService.updateChapterCoverImage(this.chapter.id, this.selectedCover).subscribe(() => {
if (this.coverImageIndex > 0) {
this.chapter.coverImageLocked = true;
this.coverImageUpdate = true;
}
this.coverImageSaveLoading = false;
}, err => this.coverImageSaveLoading = false);
} else if (this.coverImageLocked === false) {
this.uploadService.resetChapterCoverLock(this.chapter.id).subscribe(() => {
this.toastr.info('Cover image reset');
this.coverImageSaveLoading = false;
this.coverImageUpdate = true;
});
}
}
markChapterAsRead(chapter: Chapter) {
if (this.seriesId === 0) {
return;
}
this.actionService.markChapterAsRead(this.seriesId, chapter, () => { /* No Action */ });
}
markChapterAsUnread(chapter: Chapter) {
if (this.seriesId === 0) {
return;
}
this.actionService.markChapterAsUnread(this.seriesId, chapter, () => { /* No Action */ });
}
handleChapterActionCallback(action: Action, chapter: Chapter) {
switch (action) {
case(Action.MarkAsRead):
this.markChapterAsRead(chapter);
break;
case(Action.MarkAsUnread):
this.markChapterAsUnread(chapter);
break;
case(Action.AddToReadingList):
this.actionService.addChapterToReadingList(chapter, this.seriesId);
break;
default:
break;
}
}
readChapter(chapter: Chapter) {
if (chapter.pages === 0) {
this.toastr.error('There are no pages. Kavita was not able to read this archive.');
return;
}
if (chapter.files.length > 0 && chapter.files[0].format === MangaFormat.EPUB) {
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'book', chapter.id]);
} else {
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id]);
}
}
}

View file

@ -17,19 +17,6 @@
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'top' }"></ng-container>
<!-- <ng-container *ngIf="utilityService.getActiveBreakpoint() <= Breakpoint.Mobile; else cardTemplate">
<div class="d-flex justify-content-center row g-0 mt-2 mb-2">
<div class="col-auto ps-1 pe-1 mt-2 mb-2" *ngFor="let item of items; trackBy:trackByIdentity; index as i">
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
</div>
<p *ngIf="items.length === 0 && !isLoading">
<ng-container [ngTemplateOutlet]="noDataTemplate"></ng-container>
</p>
</div>
</ng-container> -->
<ng-container [ngTemplateOutlet]="cardTemplate"></ng-container>
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'bottom' }"></ng-container>

View file

@ -5,7 +5,7 @@ import { LibraryCardComponent } from './library-card/library-card.component';
import { CoverImageChooserComponent } from './cover-image-chooser/cover-image-chooser.component';
import { EditSeriesModalComponent } from './_modals/edit-series-modal/edit-series-modal.component';
import { EditCollectionTagsComponent } from './_modals/edit-collection-tags/edit-collection-tags.component';
import { NgbTooltipModule, NgbCollapseModule, NgbPaginationModule, NgbDropdownModule, NgbProgressbarModule, NgbNavModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbTooltipModule, NgbCollapseModule, NgbPaginationModule, NgbDropdownModule, NgbProgressbarModule, NgbNavModule, NgbRatingModule, NgbOffcanvasModule } from '@ng-bootstrap/ng-bootstrap';
import { CardActionablesComponent } from './card-item/card-actionables/card-actionables.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgxFileDropModule } from 'ngx-file-drop';
@ -22,6 +22,7 @@ import { ChapterMetadataDetailComponent } from './chapter-metadata-detail/chapte
import { FileInfoComponent } from './file-info/file-info.component';
import { MetadataFilterModule } from '../metadata-filter/metadata-filter.module';
import { EditSeriesRelationComponent } from './edit-series-relation/edit-series-relation.component';
import { CardDetailDrawerComponent } from './card-detail-drawer/card-detail-drawer.component';
@ -41,6 +42,7 @@ import { EditSeriesRelationComponent } from './edit-series-relation/edit-series-
ChapterMetadataDetailComponent,
FileInfoComponent,
EditSeriesRelationComponent,
CardDetailDrawerComponent,
],
imports: [
CommonModule,
@ -59,13 +61,16 @@ import { EditSeriesRelationComponent } from './edit-series-relation/edit-series-
NgbRatingModule,
NgbOffcanvasModule, // Series Detail, action of cards
NgbNavModule, //Series Detail
NgbPaginationModule, // CardDetailLayoutComponent
NgbDropdownModule,
NgbProgressbarModule,
NgxFileDropModule, // Cover Chooser
PipeModule // filter for BulkAddToCollectionComponent
PipeModule, // filter for BulkAddToCollectionComponent
SharedModule, // IconAndTitleComponent
],
exports: [
CardItemComponent,
@ -81,7 +86,9 @@ import { EditSeriesRelationComponent } from './edit-series-relation/edit-series-
CardDetailsModalComponent,
BulkOperationsComponent,
ChapterMetadataDetailComponent,
EditSeriesRelationComponent
EditSeriesRelationComponent,
NgbOffcanvasModule
]
})
export class CardsModule { }

View file

@ -1,5 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { MetadataService } from 'src/app/_services/metadata.service';
import { Chapter } from 'src/app/_models/chapter';
import { ChapterMetadata } from 'src/app/_models/chapter-metadata';
import { UtilityService } from 'src/app/shared/_services/utility.service';
@ -23,7 +22,7 @@ export class ChapterMetadataDetailComponent implements OnInit {
return LibraryType;
}
constructor(private metadataService: MetadataService, public utilityService: UtilityService) { }
constructor(public utilityService: UtilityService) { }
ngOnInit(): void {
this.roles = Object.keys(PersonRole).filter(role => /[0-9]/.test(role) === false);
@ -41,19 +40,4 @@ export class ChapterMetadataDetailComponent implements OnInit {
action.callback(action.action, chapter);
}
}
readChapter(chapter: Chapter) {
// if (chapter.pages === 0) {
// this.toastr.error('There are no pages. Kavita was not able to read this archive.');
// return;
// }
// if (chapter.files.length > 0 && chapter.files[0].format === MangaFormat.EPUB) {
// this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'book', chapter.id]);
// } else {
// this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id]);
// }
}
}