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
This commit is contained in:
Joseph Milazzo 2021-08-10 18:18:07 -05:00 committed by GitHub
parent d1d7df9291
commit e9ec6671d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1860 additions and 241 deletions

View file

@ -12,6 +12,9 @@
{{subtitle}}
</div>
</div>
<div style="margin-left: auto; padding-right: 3%;">
<button class="btn btn-icon btn-small" role="checkbox" [attr.aria-checked]="pageBookmarked" title="{{pageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}" (click)="bookmarkPage()"><i class="{{pageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i><span class="sr-only">{{pageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}</span></button>
</div>
</div>
</div>
<ng-container *ngIf="isLoading">

View file

@ -183,6 +183,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* If extended settings area is visible. Blocks auto-closing of menu.
*/
settingsOpen: boolean = false;
/**
* A map of bookmarked pages to anything. Used for O(1) lookup time if a page is bookmarked or not.
*/
bookmarks: {[key: string]: number} = {};
private readonly onDestroy = new Subject<void>();
@ -191,6 +195,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
get pageBookmarked() {
return this.bookmarks.hasOwnProperty(this.pageNum);
}
get splitIconClass() {
@ -348,13 +355,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.pageNum = 1;
forkJoin({
bookmark: this.readerService.getBookmark(this.chapterId),
chapterInfo: this.readerService.getChapterInfo(this.chapterId)
progress: this.readerService.getProgress(this.chapterId),
chapterInfo: this.readerService.getChapterInfo(this.seriesId, this.chapterId),
bookmarks: this.readerService.getBookmarks(this.chapterId)
}).pipe(take(1)).subscribe(results => {
this.volumeId = results.chapterInfo.volumeId;
this.maxPages = results.chapterInfo.pages;
let page = results.bookmark.pageNum;
let page = results.progress.pageNum;
if (page >= this.maxPages) {
page = this.maxPages - 1;
}
@ -367,6 +375,12 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateTitle(results.chapterInfo);
// From bookmarks, create map of pages to make lookup time O(1)
this.bookmarks = {};
results.bookmarks.forEach(bookmark => {
this.bookmarks[bookmark.page] = 1;
});
this.readerService.getNextChapter(this.seriesId, this.volumeId, this.chapterId).pipe(take(1)).subscribe(chapterId => {
this.nextChapterId = chapterId;
if (chapterId === CHAPTER_ID_DOESNT_EXIST || chapterId === this.chapterId) {
@ -747,14 +761,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
loadPage() {
if (!this.canvas || !this.ctx) { return; }
// Due to the fact that we start at image 0, but page 1, we need the last page to be bookmarked as page + 1 to be completed
// Due to the fact that we start at image 0, but page 1, we need the last page to have progress as page + 1 to be completed
let pageNum = this.pageNum;
if (this.pageNum == this.maxPages - 1) {
pageNum = this.pageNum + 1;
}
this.readerService.bookmark(this.seriesId, this.volumeId, this.chapterId, pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
this.isLoading = true;
this.canvasImage = this.cachedImages.current();
@ -814,13 +828,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.pageNum >= this.maxPages - 10) {
// Tell server to cache the next chapter
if (this.nextChapterId > 0 && !this.nextChapterPrefetched) {
this.readerService.getChapterInfo(this.nextChapterId).pipe(take(1)).subscribe(res => {
this.readerService.getChapterInfo(this.seriesId, this.nextChapterId).pipe(take(1)).subscribe(res => {
this.nextChapterPrefetched = true;
});
}
} else if (this.pageNum <= 10) {
if (this.prevChapterId > 0 && !this.prevChapterPrefetched) {
this.readerService.getChapterInfo(this.prevChapterId).pipe(take(1)).subscribe(res => {
this.readerService.getChapterInfo(this.seriesId, this.prevChapterId).pipe(take(1)).subscribe(res => {
this.prevChapterPrefetched = true;
});
}
@ -905,7 +919,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
handleWebtoonPageChange(updatedPageNum: number) {
this.setPageNum(updatedPageNum);
this.readerService.bookmark(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
}
saveSettings() {
@ -945,4 +959,22 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateForm();
}
/**
* Bookmarks the current page for the chapter
*/
bookmarkPage() {
const pageNum = this.pageNum;
if (this.pageBookmarked) {
// Remove bookmark
this.readerService.unbookmark(this.seriesId, this.volumeId, this.chapterId, pageNum).pipe(take(1)).subscribe(() => {
delete this.bookmarks[pageNum];
});
} else {
this.readerService.bookmark(this.seriesId, this.volumeId, this.chapterId, pageNum).pipe(take(1)).subscribe(() => {
this.bookmarks[pageNum] = 1;
});
}
}
}