Metadata Editing from the UI! (#1135)
* Added the skeleton code for layout, hooked up Age Rating, Publication Status, and Tags * Tweaked message of Scan service to Finished scan of to better indicate the total scan time * Hooked in foundation for person typeaheads * Fixed people not populating typeaheads on load * For manga/comics, when parsing, set the SeriesSort from ComicInfo if it exists. * Implemented the ability to override and create new genre tags. Code is ready to flush out the rest. * Ability to update metadata from the UI is hooked up. Next is locking. * Updated typeahead to allow for non-multiple usage. Implemented ability to update Language tag in Series Metadata. * Fixed a bug in GetContinuePoint for a case where we have Volumes, Loose Leaf chapters and no read progress. * Added ETag headers on Images to allow for better caching (bookmarks and images in manga reader) * Built out UI code to show locked indication to user * Implemented Series locking and refactored a lot of styles in typeahead to make the lock setting work, plus misc cleanup. * Added locked properties to dtos. Updated typeahead loading indicator to not interfere with close button if present * Hooked up locking flags in UI * Integrated regular field locking/unlocking * Removed some old code * Prevent input group from wrapping * Implemented some basic layout for metadata on volume/chapter card modal. Refactored out all metadata from Chapter object in terms of UI and put into a separate call to ensure speedy delivery and simplicity of code. * Refactored code to hide covers section if not an admin * Implemented ability to modify a chapter/volume cover from the detail modal * Removed a few variables and change cover image modal * Added bookmark to single chapter view * Put a temp fix in for a ngb v12 z-index bug (reported). Bumped ngb to 12.0 stable and fixed some small rendering bugs * loading buttons ftw * Lots of cleanup, looks like the story is finished * Changed action name from Info to Details * Style tweaks * Fixed an issue where Summary would assume it's locked due to a subscription firing on setting the model * Fixed some misc bugs * Code smells Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
47a92a2e01
commit
ba77954d5c
60 changed files with 3605 additions and 723 deletions
|
|
@ -9,76 +9,145 @@
|
|||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal" *ngIf="utilityService.isChapter(data)">
|
||||
<ng-container *ngIf="utilityService.isChapter(data)">
|
||||
<app-chapter-metadata-detail [chapter]="data"></app-chapter-metadata-detail>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal" *ngIf="utilityService.isVolume(data)">
|
||||
<h4 *ngIf="utilityService.isVolume(data)">Information</h4>
|
||||
|
||||
<ng-container *ngIf="utilityService.isVolume(data) || utilityService.isChapter(data)">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Id: {{data.id}}
|
||||
</div>
|
||||
<div class="col" *ngIf="series !== undefined">
|
||||
Format: <span class="badge bg-secondary">{{utilityService.mangaFormat(series.format) | sentenceCase}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
<div class="col" *ngIf="data.hasOwnProperty('created')">
|
||||
Added: {{(data.created | date: 'short') || '-'}}
|
||||
</div>
|
||||
<div class="col">
|
||||
Pages: {{data.pages}}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<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 {{libraryType !== LibraryType.Comic ? 'Chapter ' : 'Issue #'}} {{chapter.number}}">
|
||||
<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 *ngIf="chapter.number !== '0'; else specialHeader">
|
||||
<span >
|
||||
<app-card-actionables (actionHandler)="performAction($event, chapter)" [actions]="chapterActions" [labelBy]="utilityService.formatChapterName(libraryType, true, true) + formatChapterNumber(chapter)"></app-card-actionables>
|
||||
{{utilityService.formatChapterName(libraryType, true, false) }} {{formatChapterNumber(chapter)}}
|
||||
</span>
|
||||
<span class="badge bg-primary rounded-pill">
|
||||
<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>File(s)</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="modal-body scrollable-modal {{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? '' : 'd-flex'}}">
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-pills" orientation="{{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'horizontal' : 'vertical'}}" style="min-width: 135px;">
|
||||
<li [ngbNavItem]="tabs[0]" *ngIf="!tabs[0].disabled">
|
||||
<a ngbNavLink>{{tabs[0].title}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="container-fluid row g-0">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6">
|
||||
<app-image class="me-2" width="74px" [imageUrl]="chapter.coverImage"></app-image>
|
||||
ID: {{data.id}}
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Pages: {{file.pages}}
|
||||
<h4>
|
||||
{{chapter?.titleName}}
|
||||
</h4>
|
||||
<span>
|
||||
<span *ngIf="chapterMetadata && chapterMetadata.releaseDate !== null">Release Date: {{chapterMetadata.releaseDate | date: 'shortDate' || '-'}}</span>
|
||||
</span>
|
||||
<span class="text-accent">{{chapter.pages}} pages</span>
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
<div class="col-auto">
|
||||
Added: {{(chapter.created | date: 'short') || '-'}}
|
||||
</div>
|
||||
<div class="col" *ngIf="data.hasOwnProperty('created')">
|
||||
Added: {{(data.created | date: 'short') || '-'}}
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
<div class="col-auto">
|
||||
Age Rating: {{ageRating}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
<ng-container *ngIf="chapterMetadata !== undefined">
|
||||
<div class="row g-0" *ngIf="chapterMetadata.tags && chapterMetadata.tags.length > 0">
|
||||
<h6>Tags</h6>
|
||||
<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>
|
||||
</div>
|
||||
<div class="row g-0" *ngIf="chapterMetadata.genres && chapterMetadata.genres.length > 0">
|
||||
<h6>Genres</h6>
|
||||
<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>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="tabs[1]" *ngIf="!tabs[1].disabled">
|
||||
<a ngbNavLink>{{tabs[1].title}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-chapter-metadata-detail [chapter]="chapterMetadata"></app-chapter-metadata-detail>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="tabs[2]" *ngIf="!tabs[2].disabled">
|
||||
<a ngbNavLink>{{tabs[2].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[3]" *ngIf="!tabs[3].disabled">
|
||||
<a ngbNavLink>{{tabs[3].title}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row g-0">
|
||||
<ng-container *ngFor="let bookmark of bookmarks; let idx = index">
|
||||
<app-bookmark [bookmark]="bookmark" class="col-auto" (bookmarkRemoved)="removeBookmark(bookmark, idx)"></app-bookmark>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="tabs[4]" *ngIf="!tabs[4].disabled">
|
||||
<a ngbNavLink>{{tabs[4].title}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<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 {{libraryType !== LibraryType.Comic ? 'Chapter ' : 'Issue #'}} {{chapter.number}}">
|
||||
<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 *ngIf="chapter.number !== '0'; else specialHeader">
|
||||
<span>
|
||||
<app-card-actionables (actionHandler)="performAction($event, chapter)" [actions]="chapterActions"
|
||||
[labelBy]="utilityService.formatChapterName(libraryType, true, true) + formatChapterNumber(chapter)"></app-card-actionables>
|
||||
{{utilityService.formatChapterName(libraryType, true, false) }} {{formatChapterNumber(chapter)}}
|
||||
</span>
|
||||
<span class="badge bg-primary rounded-pill">
|
||||
<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>File(s)</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>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="tab-content {{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'mt-3' : 'ms-4 flex-fill'}}"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" [disabled]="!isAdmin" (click)="updateCover()">Update Cover</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Router } from '@angular/router';
|
|||
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import { MangaFile } from 'src/app/_models/manga-file';
|
||||
import { MangaFormat } from 'src/app/_models/manga-format';
|
||||
|
|
@ -12,11 +12,16 @@ import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/acti
|
|||
import { ActionService } from 'src/app/_services/action.service';
|
||||
import { ImageService } from 'src/app/_services/image.service';
|
||||
import { UploadService } from 'src/app/_services/upload.service';
|
||||
import { ChangeCoverImageModalComponent } from '../change-cover-image/change-cover-image-modal.component';
|
||||
import { LibraryType } from '../../../_models/library';
|
||||
import { LibraryService } from '../../../_services/library.service';
|
||||
import { SeriesService } from 'src/app/_services/series.service';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { PersonRole } from 'src/app/_models/person';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
import { ChapterMetadata } from 'src/app/_models/chapter-metadata';
|
||||
import { PageBookmark } from 'src/app/_models/page-bookmark';
|
||||
import { ReaderService } from 'src/app/_services/reader.service';
|
||||
import { MetadataService } from 'src/app/_services/metadata.service';
|
||||
|
||||
|
||||
|
||||
|
|
@ -30,38 +35,95 @@ export class CardDetailsModalComponent implements OnInit {
|
|||
@Input() parentName = '';
|
||||
@Input() seriesId: number = 0;
|
||||
@Input() libraryId: number = 0;
|
||||
@Input() data!: any; // Volume | Chapter
|
||||
@Input() data!: Volume | Chapter; // Volume | Chapter
|
||||
|
||||
/**
|
||||
* If this is a volume, this will be first chapter for said volume.
|
||||
*/
|
||||
chapter!: Chapter;
|
||||
isChapter = false;
|
||||
chapters: Chapter[] = [];
|
||||
seriesVolumes: any[] = [];
|
||||
isLoadingVolumes = false;
|
||||
formatKeys = Object.keys(MangaFormat);
|
||||
|
||||
|
||||
/**
|
||||
* If a cover image update occured.
|
||||
*/
|
||||
coverImageUpdate: boolean = false;
|
||||
isAdmin: 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;
|
||||
series: Series | undefined = undefined;
|
||||
|
||||
bookmarks: PageBookmark[] = [];
|
||||
|
||||
tabs = [{title: 'General', disabled: false}, {title: 'Metadata', disabled: false}, {title: 'Cover', disabled: false}, {title: 'Bookmarks', disabled: false}, {title: 'Info', disabled: false}];
|
||||
active = this.tabs[0];
|
||||
|
||||
chapterMetadata!: ChapterMetadata;
|
||||
ageRating!: string;
|
||||
|
||||
|
||||
get Breakpoint(): typeof Breakpoint {
|
||||
return Breakpoint;
|
||||
}
|
||||
|
||||
get PersonRole() {
|
||||
return PersonRole;
|
||||
}
|
||||
|
||||
get LibraryType(): typeof LibraryType {
|
||||
return LibraryType;
|
||||
}
|
||||
|
||||
constructor(private modalService: NgbModal, public modal: NgbActiveModal, public utilityService: UtilityService,
|
||||
constructor(public modal: NgbActiveModal, 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 seriesService: SeriesService, private readerService: ReaderService, public metadataService: MetadataService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isChapter = this.utilityService.isChapter(this.data);
|
||||
console.log('isChapter: ', this.isChapter);
|
||||
|
||||
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));
|
||||
|
||||
let bookmarkApi;
|
||||
if (this.isChapter) {
|
||||
bookmarkApi = this.readerService.getBookmarks(this.chapter.id);
|
||||
} else {
|
||||
bookmarkApi = this.readerService.getBookmarksForVolume(this.data.id);
|
||||
}
|
||||
|
||||
bookmarkApi.pipe(take(1)).subscribe(bookmarks => {
|
||||
this.bookmarks = bookmarks;
|
||||
});
|
||||
|
||||
this.seriesService.getChapterMetadata(this.chapter.id).subscribe(metadata => {
|
||||
this.chapterMetadata = metadata;
|
||||
|
||||
this.metadataService.getAgeRating(this.chapterMetadata.ageRating).subscribe(ageRating => this.ageRating = ageRating);
|
||||
});
|
||||
|
||||
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||
if (!this.accountService.hasAdminRole(user)) {
|
||||
this.tabs.find(s => s.title === 'Cover')!.disabled = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -72,10 +134,11 @@ export class CardDetailsModalComponent implements OnInit {
|
|||
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this)).filter(item => item.action !== Action.Edit);
|
||||
|
||||
if (this.isChapter) {
|
||||
this.chapters.push(this.data);
|
||||
this.chapters.push(this.data as Chapter);
|
||||
} else if (!this.isChapter) {
|
||||
this.chapters.push(...this.data?.chapters);
|
||||
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
|
||||
|
|
@ -83,10 +146,6 @@ export class CardDetailsModalComponent implements OnInit {
|
|||
this.chapters.forEach((c: Chapter) => {
|
||||
c.files.sort((a: MangaFile, b: MangaFile) => collator.compare(a.filePath, b.filePath));
|
||||
});
|
||||
|
||||
this.seriesService.getSeries(this.seriesId).subscribe(series => {
|
||||
this.series = series;
|
||||
})
|
||||
}
|
||||
|
||||
close() {
|
||||
|
|
@ -106,34 +165,36 @@ export class CardDetailsModalComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
updateCover() {
|
||||
const modalRef = this.modalService.open(ChangeCoverImageModalComponent, { size: 'lg' }); // scrollable: true, size: 'lg', windowClass: 'scrollable-modal' (these don't work well on mobile)
|
||||
if (this.utilityService.isChapter(this.data)) {
|
||||
const chapter = this.utilityService.asChapter(this.data)
|
||||
chapter.coverImage = this.imageService.getChapterCoverImage(chapter.id);
|
||||
modalRef.componentInstance.chapter = chapter;
|
||||
modalRef.componentInstance.title = 'Select ' + (chapter.isSpecial ? '' : this.utilityService.formatChapterName(this.libraryType, false, true)) + chapter.range + '\'s Cover';
|
||||
} else {
|
||||
const volume = this.utilityService.asVolume(this.data);
|
||||
const chapters = volume.chapters;
|
||||
if (chapters && chapters.length > 0) {
|
||||
modalRef.componentInstance.chapter = chapters[0];
|
||||
modalRef.componentInstance.title = 'Select Volume ' + volume.number + '\'s Cover';
|
||||
}
|
||||
}
|
||||
|
||||
modalRef.closed.subscribe((closeResult: {success: boolean, chapter: Chapter, coverImageUpdate: boolean}) => {
|
||||
if (closeResult.success) {
|
||||
this.coverImageUpdate = closeResult.coverImageUpdate;
|
||||
if (!this.coverImageUpdate) {
|
||||
this.uploadService.resetChapterCoverLock(closeResult.chapter.id).subscribe(() => {
|
||||
this.toastr.info('Please refresh in a bit for the cover image to be reflected.');
|
||||
});
|
||||
} else {
|
||||
closeResult.chapter.coverImage = this.imageService.randomize(this.imageService.getChapterCoverImage(closeResult.chapter.id));
|
||||
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) {
|
||||
|
|
@ -180,4 +241,10 @@ export class CardDetailsModalComponent implements OnInit {
|
|||
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id]);
|
||||
}
|
||||
}
|
||||
|
||||
removeBookmark(bookmark: PageBookmark, index: number) {
|
||||
this.readerService.unbookmark(bookmark.seriesId, bookmark.volumeId, bookmark.chapterId, bookmark.page).subscribe(() => {
|
||||
this.bookmarks.splice(index, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue