Custom Cover Images (#499)
* Added some documentation. Removed Require Admin Role from Search Tags. Added Summary to be updated on UpdateTag. * Added Swagger xml doc generation to beef up the documentation. Started adding xml comments to the APIs. This is a needed, slow task for upcoming Plugins system. * Implemented the ability to upload a custom series image to override the existing cover image. Refactored some code out to use ImageService and added more documentation * When a page cache fails, delete cache directory so user can try to reload. * Implemented the ability to lock a series cover image such that after user uploads something, it wont get refreshed by Kavita. * Implemented the ability to reset cover image for series by unlocking * Kick off a series refresh after a cover is unlocked. * Ability to press enter to load a url * Ability to reset selection * Cleaned up cover chooser such that reset is nicer, errors inform user to use file upload, series edit modal now doesn't use scrollable body. Mobile tweaks. CoverImageLocked is now sent to the UI. * More css changes to look better * When no bookmarks, don't show both markups * Fixed issues where images wouldn't refresh after cover image was changed. * Implemented the ability to change the cover images for collection tags. * Added property and API for chapter cover image update * Added UI code to prepare for updating cover image for chapters. need to rearrange components * Moved a ton of code around to separate card related screens into their own module. * Implemented the ability to update a chapter/volume cover image * Refactored action for volume to say edit to reflect modal action * Fixed issue where after editing chapter cover image, the underlying card wouldn't update * Fixed an issue where we were passing volumeId to the reset chapter lock. Changed some logic in volume cover image generation. * Automatically apply when you hit reset cover image
This commit is contained in:
parent
30387bc370
commit
2fd02f0d2b
95 changed files with 3364 additions and 20668 deletions
|
|
@ -0,0 +1,65 @@
|
|||
<div *ngIf="data !== undefined">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">
|
||||
{{parentName}} - {{data.number != 0 ? (isChapter ? 'Chapter ' : 'Volume ') + data.number : 'Special'}} Details</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal">
|
||||
<h4 *ngIf="utilityService.isVolume(data)">Information</h4>
|
||||
|
||||
<ng-container *ngIf="utilityService.isVolume(data) || utilityService.isChapter(data)">
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
Id: {{data.id}}
|
||||
</div>
|
||||
<div class="col">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="col" *ngIf="utilityService.isVolume(data)">
|
||||
Added: {{(data.created | date: 'MM/dd/yyyy') || '-'}}
|
||||
</div>
|
||||
<div class="col">
|
||||
Pages: {{data.pages}}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<h4 *ngIf="!utilityService.isChapter(data)">Chapters</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li class="media my-4" *ngFor="let chapter of chapters">
|
||||
<img class="mr-3" style="width: 74px" src="{{imageService.randomize(imageService.getChapterCoverImage(chapter.id))}}">
|
||||
<div class="media-body">
|
||||
<h5 class="mt-0 mb-1">
|
||||
<span *ngIf="chapter.number !== '0'; else specialHeader">
|
||||
Chapter {{formatChapterNumber(chapter)}}
|
||||
</span>
|
||||
<ng-template #specialHeader>File(s)</ng-template>
|
||||
</h5>
|
||||
<ul class="list-group">
|
||||
<li *ngFor="let file of chapter.files" class="list-group-item">
|
||||
<span>{{file.filePath}}</span>
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
Pages: {{file.pages}}
|
||||
</div>
|
||||
<div class="col">
|
||||
Format: <span class="badge badge-secondary">{{utilityService.mangaFormatToText(file.format)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-info" (click)="updateCover()">Update Cover</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.scrollable-modal {
|
||||
max-height: 90vh; // 600px
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { 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';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
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';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-card-details-modal',
|
||||
templateUrl: './card-details-modal.component.html',
|
||||
styleUrls: ['./card-details-modal.component.scss']
|
||||
})
|
||||
export class CardDetailsModalComponent implements OnInit {
|
||||
|
||||
@Input() parentName = '';
|
||||
@Input() seriesId: number = 0;
|
||||
@Input() data!: any; // Volume | Chapter
|
||||
isChapter = false;
|
||||
chapters: Chapter[] = [];
|
||||
seriesVolumes: any[] = [];
|
||||
isLoadingVolumes = false;
|
||||
formatKeys = Object.keys(MangaFormat);
|
||||
/**
|
||||
* If a cover image update occured.
|
||||
*/
|
||||
coverImageUpdate: boolean = false;
|
||||
|
||||
|
||||
constructor(private modalService: NgbModal, public modal: NgbActiveModal, public utilityService: UtilityService,
|
||||
public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isChapter = this.utilityService.isChapter(this.data);
|
||||
|
||||
if (this.isChapter) {
|
||||
this.chapters.push(this.data);
|
||||
} else if (!this.isChapter) {
|
||||
this.chapters.push(...this.data?.chapters);
|
||||
}
|
||||
this.chapters.sort(this.utilityService.sortChapters);
|
||||
// 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.modal.close({coverImageUpdate: this.coverImageUpdate});
|
||||
}
|
||||
|
||||
formatChapterNumber(chapter: Chapter) {
|
||||
if (chapter.number === '0') {
|
||||
return '1';
|
||||
}
|
||||
return chapter.number;
|
||||
}
|
||||
|
||||
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)
|
||||
modalRef.componentInstance.chapter = chapter;
|
||||
modalRef.componentInstance.title = 'Select ' + (chapter.isSpecial ? '' : 'Chapter ') + 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.');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue