Kavita/UI/Web/src/app/_services/reader.service.ts
Joseph Milazzo e9ec6671d5
Bookmarking Pages within the Reader (#469)
# Added
- Added: Added the ability to bookmark certain pages within the manga (image) reader and later download them from the series context menu. 

# Fixed
- Fixed: Fixed an issue where after adding a new folder to an existing library, a scan wouldn't be kicked off
- Fixed: In some cases, after clicking the background of a modal, the modal would close, but state wouldn't be handled as if cancel was pushed

# Changed
- Changed: Admin contextual actions on cards will now be separated by a line to help differentiate. 
- Changed: Performance enhancement on an API used before reading

# Dev
- Bumped dependencies to latest versions

=============================================
* Bumped versions of dependencies and refactored bookmark to progress.

* Refactored method names in UI from bookmark to progress to prepare for new bookmark entity

* Basic code is done, user can now bookmark a page (currently image reader only).

* Comments and pipes

* Some accessibility for new bookmark button

* Fixed up the APIs to work correctly, added a new modal to quickly explore bookmarks (not implemented, not final).

* Cleanup on the UI side to get the modal to look decent

* Added dismissed handlers for modals where appropriate

* Refactored UI to only show number of bookmarks across files to simplify delivery. Admin actionables are now separated by hr vs non-admin actions.

* Basic API implemented, now to implement the ability to actually extract files.

* Implemented the ability to download bookmarks.

* Fixed a bug where adding a new folder to an existing library would not trigger a scan library task.

* Fixed an issue that could cause bookmarked pages to get copied out of order.

* Added handler from series-card component
2021-08-10 18:18:07 -05:00

131 lines
5 KiB
TypeScript

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { ChapterInfo } from '../manga-reader/_models/chapter-info';
import { UtilityService } from '../shared/_services/utility.service';
import { Chapter } from '../_models/chapter';
import { PageBookmark } from '../_models/page-bookmark';
import { ProgressBookmark } from '../_models/progress-bookmark';
import { Volume } from '../_models/volume';
@Injectable({
providedIn: 'root'
})
export class ReaderService {
baseUrl = environment.apiUrl;
// Override background color for reader and restore it onDestroy
private originalBodyColor!: string;
constructor(private httpClient: HttpClient, private utilityService: UtilityService) { }
bookmark(seriesId: number, volumeId: number, chapterId: number, page: number) {
return this.httpClient.post(this.baseUrl + 'reader/bookmark', {seriesId, volumeId, chapterId, page});
}
unbookmark(seriesId: number, volumeId: number, chapterId: number, page: number) {
return this.httpClient.post(this.baseUrl + 'reader/unbookmark', {seriesId, volumeId, chapterId, page});
}
getBookmarks(chapterId: number) {
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/get-bookmarks?chapterId=' + chapterId);
}
getBookmarksForVolume(volumeId: number) {
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/get-volume-bookmarks?volumeId=' + volumeId);
}
getBookmarksForSeries(seriesId: number) {
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/get-series-bookmarks?seriesId=' + seriesId);
}
clearBookmarks(seriesId: number) {
return this.httpClient.post(this.baseUrl + 'reader/remove-bookmarks', {seriesId});
}
getProgress(chapterId: number) {
return this.httpClient.get<ProgressBookmark>(this.baseUrl + 'reader/get-progress?chapterId=' + chapterId);
}
getPageUrl(chapterId: number, page: number) {
return this.baseUrl + 'reader/image?chapterId=' + chapterId + '&page=' + page;
}
getChapterInfo(seriesId: number, chapterId: number) {
return this.httpClient.get<ChapterInfo>(this.baseUrl + 'reader/chapter-info?chapterId=' + chapterId + '&seriesId=' + seriesId);
}
saveProgress(seriesId: number, volumeId: number, chapterId: number, page: number, bookScrollId: string | null = null) {
return this.httpClient.post(this.baseUrl + 'reader/progress', {seriesId, volumeId, chapterId, pageNum: page, bookScrollId});
}
markVolumeRead(seriesId: number, volumeId: number) {
return this.httpClient.post(this.baseUrl + 'reader/mark-volume-read', {seriesId, volumeId});
}
getNextChapter(seriesId: number, volumeId: number, currentChapterId: number) {
return this.httpClient.get<number>(this.baseUrl + 'reader/next-chapter?seriesId=' + seriesId + '&volumeId=' + volumeId + '&currentChapterId=' + currentChapterId);
}
getPrevChapter(seriesId: number, volumeId: number, currentChapterId: number) {
return this.httpClient.get<number>(this.baseUrl + 'reader/prev-chapter?seriesId=' + seriesId + '&volumeId=' + volumeId + '&currentChapterId=' + currentChapterId);
}
getCurrentChapter(volumes: Array<Volume>): Chapter {
let currentlyReadingChapter: Chapter | undefined = undefined;
const chapters = volumes.filter(v => v.number !== 0).map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters); // changed from === 0 to != 0
for (const c of chapters) {
if (c.pagesRead < c.pages) {
currentlyReadingChapter = c;
break;
}
}
if (currentlyReadingChapter === undefined) {
// Check if there are specials we can load:
const specials = volumes.filter(v => v.number === 0).map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters);
for (const c of specials) {
if (c.pagesRead < c.pages) {
currentlyReadingChapter = c;
break;
}
}
if (currentlyReadingChapter === undefined) {
// Default to first chapter
currentlyReadingChapter = chapters[0];
}
}
return currentlyReadingChapter;
}
/**
* Captures current body color and forces background color to be black. Call @see resetOverrideStyles() on destroy of component to revert changes
*/
setOverrideStyles() {
const bodyNode = document.querySelector('body');
if (bodyNode !== undefined && bodyNode !== null) {
this.originalBodyColor = bodyNode.style.background;
bodyNode.setAttribute('style', 'background-color: black !important');
}
}
resetOverrideStyles() {
const bodyNode = document.querySelector('body');
if (bodyNode !== undefined && bodyNode !== null && this.originalBodyColor !== undefined) {
bodyNode.style.background = this.originalBodyColor;
}
}
/**
* Parses out the page number from a Image src url
* @param imageSrc Src attribute of Image
* @returns
*/
imageUrlToPageNum(imageSrc: string) {
if (imageSrc === undefined || imageSrc === '') { return -1; }
return parseInt(imageSrc.split('&page=')[1], 10);
}
}