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:
parent
d796bcdc0a
commit
63475722ea
40 changed files with 883 additions and 144 deletions
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue