Readable Bookmarks (#1228)

* Moved bookmarks to it's own page on side nav and integrated actions.

* Implemented the ability to read bookmarks in the manga reader.

* Removed old bookmark components that aren't needed any longer.

* Removed recently added component as we use all-series instead now

* Removed bookmark tab from card detail

* Fixed scroll to top not working and being missing

* When opening the side nav on mobile with metadata filter already open, collapse the filter.

* When on mobile viewports, when clicking an item from side nav, collapse it afterwards

* Converted most of series detail to use the card detail layout, except storyline which has custom logic

* Fixed unit test
This commit is contained in:
Joseph Milazzo 2022-04-23 13:58:14 -05:00 committed by GitHub
parent 62715a9977
commit 9d6843614d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 648 additions and 634 deletions

View file

@ -1,29 +0,0 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}} Bookmarks</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
</button>
</div>
<div class="modal-body">
<p *ngIf="bookmarks.length > 0; else noBookmarks">
There are {{bookmarks.length}} pages bookmarked over {{uniqueChapters}} files.
</p>
<ng-template #noBookmarks>No bookmarks yet</ng-template>
<div class="row g-0">
<div *ngFor="let bookmark of bookmarks; let idx = index">
<app-bookmark [bookmark]="bookmark" (bookmarkRemoved)="removeBookmark(bookmark, idx)" class="col-auto"></app-bookmark>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="clearBookmarks()" [disabled]="(isDownloading || isClearing) || bookmarks.length === 0">
<span *ngIf="isClearing" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span>Clear{{isClearing ? 'ing...' : ''}}</span>
</button>
<button type="button" class="btn btn-secondary" (click)="downloadBookmarks()" [disabled]="(isDownloading || isClearing) || bookmarks.length === 0">
<span *ngIf="isDownloading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span>Download{{isDownloading ? 'ing...' : ''}}</span>
</button>
<button type="button" class="btn btn-primary" (click)="close()">Close</button>
</div>

View file

@ -1,77 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { finalize, take, takeWhile } from 'rxjs/operators';
import { DownloadService } from 'src/app/shared/_services/download.service';
import { PageBookmark } from 'src/app/_models/page-bookmark';
import { Series } from 'src/app/_models/series';
import { ImageService } from 'src/app/_services/image.service';
import { ReaderService } from 'src/app/_services/reader.service';
@Component({
selector: 'app-bookmarks-modal',
templateUrl: './bookmarks-modal.component.html',
styleUrls: ['./bookmarks-modal.component.scss']
})
export class BookmarksModalComponent implements OnInit {
@Input() series!: Series;
bookmarks: Array<PageBookmark> = [];
title: string = '';
subtitle: string = '';
isDownloading: boolean = false;
isClearing: boolean = false;
uniqueChapters: number = 0;
constructor(public imageService: ImageService, private readerService: ReaderService,
public modal: NgbActiveModal, private downloadService: DownloadService,
private toastr: ToastrService) { }
ngOnInit(): void {
this.init();
}
init() {
this.readerService.getBookmarksForSeries(this.series.id).pipe(take(1)).subscribe(bookmarks => {
this.bookmarks = bookmarks;
const chapters: {[id: number]: string} = {};
this.bookmarks.forEach(bmk => {
if (!chapters.hasOwnProperty(bmk.chapterId)) {
chapters[bmk.chapterId] = '';
}
});
this.uniqueChapters = Object.keys(chapters).length;
});
}
close() {
this.modal.close();
}
removeBookmark(bookmark: PageBookmark, index: number) {
this.bookmarks.splice(index, 1);
}
downloadBookmarks() {
this.isDownloading = true;
this.downloadService.downloadBookmarks(this.bookmarks).pipe(
takeWhile(val => {
return val.state != 'DONE';
}),
finalize(() => {
this.isDownloading = false;
})).subscribe(() => {/* No Operation */});
}
clearBookmarks() {
this.isClearing = true;
this.readerService.clearBookmarks(this.series.id).subscribe(() => {
this.isClearing = false;
this.init();
this.toastr.success(this.series.name + '\'s bookmarks have been removed');
});
}
}

View file

@ -89,20 +89,6 @@
</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>
<ng-container *ngIf="bookmarks.length === 0">
No bookmarks yet
</ng-container>
</div>
</ng-template>
</li>
<li [ngbNavItem]="tabs[4]" *ngIf="!tabs[4].disabled">
<a ngbNavLink>{{tabs[4].title}}</a>
<ng-template ngbNavContent>

View file

@ -66,9 +66,8 @@ export class CardDetailsModalComponent implements OnInit {
chapterActions: ActionItem<Chapter>[] = [];
libraryType: LibraryType = LibraryType.Manga;
bookmarks: PageBookmark[] = [];
tabs = [{title: 'General', disabled: false}, {title: 'Metadata', disabled: false}, {title: 'Cover', disabled: false}, {title: 'Bookmarks', disabled: false}, {title: 'Info', disabled: false}];
tabs = [{title: 'General', disabled: false}, {title: 'Metadata', disabled: false}, {title: 'Cover', disabled: false}, {title: 'Info', disabled: false}];
active = this.tabs[0];
chapterMetadata!: ChapterMetadata;
@ -100,17 +99,6 @@ export class CardDetailsModalComponent implements OnInit {
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;
@ -240,10 +228,4 @@ 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);
});
}
}

View file

@ -3,7 +3,7 @@ import { NavigationStart, Router } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Action, ActionFactoryService } from '../_services/action-factory.service';
type DataSource = 'volume' | 'chapter' | 'special' | 'series';
type DataSource = 'volume' | 'chapter' | 'special' | 'series' | 'bookmark';
/**
* Responsible for handling selections on cards. Can handle multiple card sources next to each other in different loops.
@ -132,6 +132,10 @@ export class BulkSelectionService {
return this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
}
if (Object.keys(this.selectedCards).filter(item => item === 'bookmark').length > 0) {
return this.actionFactory.getBookmarkActions(callback);
}
return this.actionFactory.getVolumeActions(callback).filter(item => allowedActions.includes(item.action));
}

View file

@ -24,7 +24,7 @@
</div>
<p *ngIf="items.length === 0 && !isLoading">
There is no data
<ng-container [ngTemplateOutlet]="noDataTemplate"></ng-container>
</p>
</div>
@ -73,3 +73,8 @@
</div>
</ng-template>
<div class="mx-auto" *ngIf="isLoading" style="width: 200px;">
<div class="spinner-border text-secondary loading" role="status">
<span class="invisible">Loading...</span>
</div>
</div>

View file

@ -38,6 +38,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
@Output() applyFilter: EventEmitter<FilterEvent> = new EventEmitter();
@ContentChild('cardItem') itemTemplate!: TemplateRef<any>;
@ContentChild('noData') noDataTemplate!: TemplateRef<any>;
// Filter Code

View file

@ -5,7 +5,6 @@ import { LibraryCardComponent } from './library-card/library-card.component';
import { CoverImageChooserComponent } from './cover-image-chooser/cover-image-chooser.component';
import { EditSeriesModalComponent } from './_modals/edit-series-modal/edit-series-modal.component';
import { EditCollectionTagsComponent } from './_modals/edit-collection-tags/edit-collection-tags.component';
import { BookmarksModalComponent } from './_modals/bookmarks-modal/bookmarks-modal.component';
import { NgbTooltipModule, NgbCollapseModule, NgbPaginationModule, NgbDropdownModule, NgbProgressbarModule, NgbNavModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
import { CardActionablesComponent } from './card-item/card-actionables/card-actionables.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -21,7 +20,6 @@ import { BulkAddToCollectionComponent } from './_modals/bulk-add-to-collection/b
import { PipeModule } from '../pipe/pipe.module';
import { ChapterMetadataDetailComponent } from './chapter-metadata-detail/chapter-metadata-detail.component';
import { FileInfoComponent } from './file-info/file-info.component';
import { BookmarkComponent } from './bookmark/bookmark.component';
import { MetadataFilterModule } from '../metadata-filter/metadata-filter.module';
@ -34,7 +32,6 @@ import { MetadataFilterModule } from '../metadata-filter/metadata-filter.module'
CoverImageChooserComponent,
EditSeriesModalComponent,
EditCollectionTagsComponent,
BookmarksModalComponent,
CardActionablesComponent,
CardDetailLayoutComponent,
CardDetailsModalComponent,
@ -42,7 +39,6 @@ import { MetadataFilterModule } from '../metadata-filter/metadata-filter.module'
BulkAddToCollectionComponent,
ChapterMetadataDetailComponent,
FileInfoComponent,
BookmarkComponent,
],
imports: [
CommonModule,
@ -79,7 +75,6 @@ import { MetadataFilterModule } from '../metadata-filter/metadata-filter.module'
CoverImageChooserComponent,
EditSeriesModalComponent,
EditCollectionTagsComponent,
BookmarksModalComponent,
CardActionablesComponent,
CardDetailLayoutComponent,
CardDetailsModalComponent,

View file

@ -96,9 +96,6 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
case(Action.Edit):
this.openEditModal(series);
break;
case(Action.Bookmarks):
this.actionService.openBookmarkModal(series, (series) => {/* No Operation */ });
break;
case(Action.AddToReadingList):
this.actionService.addSeriesToReadingList(series, (series) => {/* No Operation */ });
break;