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:
parent
d1d7df9291
commit
e9ec6671d5
49 changed files with 1860 additions and 241 deletions
|
|
@ -14,14 +14,15 @@ export enum Action {
|
|||
Edit = 4,
|
||||
Info = 5,
|
||||
RefreshMetadata = 6,
|
||||
Download = 7
|
||||
Download = 7,
|
||||
Bookmarks = 8
|
||||
}
|
||||
|
||||
export interface ActionItem<T> {
|
||||
title: string;
|
||||
action: Action;
|
||||
callback: (action: Action, data: T) => void;
|
||||
|
||||
requiresAdmin: boolean;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -58,43 +59,50 @@ export class ActionFactoryService {
|
|||
this.collectionTagActions.push({
|
||||
action: Action.Edit,
|
||||
title: 'Edit',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: true
|
||||
});
|
||||
|
||||
this.seriesActions.push({
|
||||
action: Action.ScanLibrary,
|
||||
title: 'Scan Series',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: true
|
||||
});
|
||||
|
||||
this.seriesActions.push({
|
||||
action: Action.RefreshMetadata,
|
||||
title: 'Refresh Metadata',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: true
|
||||
});
|
||||
|
||||
this.seriesActions.push({
|
||||
action: Action.Delete,
|
||||
title: 'Delete',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: true
|
||||
});
|
||||
|
||||
this.seriesActions.push({
|
||||
action: Action.Edit,
|
||||
title: 'Edit',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: true
|
||||
});
|
||||
|
||||
this.libraryActions.push({
|
||||
action: Action.ScanLibrary,
|
||||
title: 'Scan Library',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: true
|
||||
});
|
||||
|
||||
this.libraryActions.push({
|
||||
action: Action.RefreshMetadata,
|
||||
title: 'Refresh Metadata',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -102,13 +110,15 @@ export class ActionFactoryService {
|
|||
this.volumeActions.push({
|
||||
action: Action.Download,
|
||||
title: 'Download',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
});
|
||||
|
||||
this.chapterActions.push({
|
||||
action: Action.Download,
|
||||
title: 'Download',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -150,12 +160,20 @@ export class ActionFactoryService {
|
|||
{
|
||||
action: Action.MarkAsRead,
|
||||
title: 'Mark as Read',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.MarkAsUnread,
|
||||
title: 'Mark as Unread',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.Bookmarks,
|
||||
title: 'Bookmarks',
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -163,12 +181,14 @@ export class ActionFactoryService {
|
|||
{
|
||||
action: Action.MarkAsRead,
|
||||
title: 'Mark as Read',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.MarkAsUnread,
|
||||
title: 'Mark as Unread',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -176,25 +196,29 @@ export class ActionFactoryService {
|
|||
{
|
||||
action: Action.MarkAsRead,
|
||||
title: 'Mark as Read',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.MarkAsUnread,
|
||||
title: 'Mark as Unread',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
}
|
||||
];
|
||||
|
||||
this.volumeActions.push({
|
||||
action: Action.Info,
|
||||
title: 'Info',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
});
|
||||
|
||||
this.chapterActions.push({
|
||||
action: Action.Info,
|
||||
title: 'Info',
|
||||
callback: this.dummyCallback
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { forkJoin, Subject } from 'rxjs';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
import { BookmarksModalComponent } from '../_modals/bookmarks-modal/bookmarks-modal.component';
|
||||
import { Chapter } from '../_models/chapter';
|
||||
import { Library } from '../_models/library';
|
||||
import { Series } from '../_models/series';
|
||||
|
|
@ -24,9 +26,10 @@ export type ChapterActionCallback = (chapter: Chapter) => void;
|
|||
export class ActionService implements OnDestroy {
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
private bookmarkModalRef: NgbModalRef | null = null;
|
||||
|
||||
constructor(private libraryService: LibraryService, private seriesService: SeriesService,
|
||||
private readerService: ReaderService, private toastr: ToastrService) { }
|
||||
private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy.next();
|
||||
|
|
@ -153,7 +156,7 @@ export class ActionService implements OnDestroy {
|
|||
* @param callback Optional callback to perform actions after API completes
|
||||
*/
|
||||
markVolumeAsUnread(seriesId: number, volume: Volume, callback?: VolumeActionCallback) {
|
||||
forkJoin(volume.chapters?.map(chapter => this.readerService.bookmark(seriesId, volume.id, chapter.id, 0))).pipe(takeUntil(this.onDestroy)).subscribe(results => {
|
||||
forkJoin(volume.chapters?.map(chapter => this.readerService.saveProgress(seriesId, volume.id, chapter.id, 0))).pipe(takeUntil(this.onDestroy)).subscribe(results => {
|
||||
volume.pagesRead = 0;
|
||||
volume.chapters?.forEach(c => c.pagesRead = 0);
|
||||
this.toastr.success('Marked as Unread');
|
||||
|
|
@ -170,7 +173,7 @@ export class ActionService implements OnDestroy {
|
|||
* @param callback Optional callback to perform actions after API completes
|
||||
*/
|
||||
markChapterAsRead(seriesId: number, chapter: Chapter, callback?: ChapterActionCallback) {
|
||||
this.readerService.bookmark(seriesId, chapter.volumeId, chapter.id, chapter.pages).pipe(take(1)).subscribe(results => {
|
||||
this.readerService.saveProgress(seriesId, chapter.volumeId, chapter.id, chapter.pages).pipe(take(1)).subscribe(results => {
|
||||
chapter.pagesRead = chapter.pages;
|
||||
this.toastr.success('Marked as Read');
|
||||
if (callback) {
|
||||
|
|
@ -186,7 +189,7 @@ export class ActionService implements OnDestroy {
|
|||
* @param callback Optional callback to perform actions after API completes
|
||||
*/
|
||||
markChapterAsUnread(seriesId: number, chapter: Chapter, callback?: ChapterActionCallback) {
|
||||
this.readerService.bookmark(seriesId, chapter.volumeId, chapter.id, chapter.pages).pipe(take(1)).subscribe(results => {
|
||||
this.readerService.saveProgress(seriesId, chapter.volumeId, chapter.id, chapter.pages).pipe(take(1)).subscribe(results => {
|
||||
chapter.pagesRead = 0;
|
||||
this.toastr.success('Marked as unread');
|
||||
if (callback) {
|
||||
|
|
@ -195,5 +198,23 @@ export class ActionService implements OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
openBookmarkModal(series: Series, callback?: SeriesActionCallback) {
|
||||
if (this.bookmarkModalRef != null) { return; }
|
||||
this.bookmarkModalRef = this.modalService.open(BookmarksModalComponent, { scrollable: true, size: 'lg' });
|
||||
this.bookmarkModalRef.componentInstance.series = series;
|
||||
this.bookmarkModalRef.closed.pipe(take(1)).subscribe(() => {
|
||||
this.bookmarkModalRef = null;
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
});
|
||||
this.bookmarkModalRef.dismissed.pipe(take(1)).subscribe(() => {
|
||||
this.bookmarkModalRef = null;
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ export class ImageService {
|
|||
return this.baseUrl + 'image/chapter-cover?chapterId=' + chapterId;
|
||||
}
|
||||
|
||||
getBookmarkedImage(chapterId: number, pageNum: number) {
|
||||
return this.baseUrl + 'image/chapter-cover?chapterId=' + chapterId + '&pageNum=' + pageNum;
|
||||
}
|
||||
|
||||
updateErroredImage(event: any) {
|
||||
event.target.src = this.placeholderImage;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ export class MessageHubService {
|
|||
this.updateNotificationModalRef.closed.subscribe(() => {
|
||||
this.updateNotificationModalRef = null;
|
||||
});
|
||||
this.updateNotificationModalRef.dismissed.subscribe(() => {
|
||||
this.updateNotificationModalRef = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ 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 { Bookmark } from '../_models/bookmark';
|
||||
import { Chapter } from '../_models/chapter';
|
||||
import { PageBookmark } from '../_models/page-bookmark';
|
||||
import { ProgressBookmark } from '../_models/progress-bookmark';
|
||||
import { Volume } from '../_models/volume';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -19,20 +20,44 @@ export class ReaderService {
|
|||
|
||||
constructor(private httpClient: HttpClient, private utilityService: UtilityService) { }
|
||||
|
||||
getBookmark(chapterId: number) {
|
||||
return this.httpClient.get<Bookmark>(this.baseUrl + 'reader/get-bookmark?chapterId=' + chapterId);
|
||||
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(chapterId: number) {
|
||||
return this.httpClient.get<ChapterInfo>(this.baseUrl + 'reader/chapter-info?chapterId=' + chapterId);
|
||||
getChapterInfo(seriesId: number, chapterId: number) {
|
||||
return this.httpClient.get<ChapterInfo>(this.baseUrl + 'reader/chapter-info?chapterId=' + chapterId + '&seriesId=' + seriesId);
|
||||
}
|
||||
|
||||
bookmark(seriesId: number, volumeId: number, chapterId: number, page: number, bookScrollId: string | null = null) {
|
||||
return this.httpClient.post(this.baseUrl + 'reader/bookmark', {seriesId, volumeId, chapterId, pageNum: page, bookScrollId});
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue