UX Overhaul Part 2 (#3112)
Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
0247bc5012
commit
3d8aa2ad24
192 changed files with 14808 additions and 1874 deletions
178
UI/Web/src/app/volume-detail/volume-detail.component.html
Normal file
178
UI/Web/src/app/volume-detail/volume-detail.component.html
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
<ng-container *transloco="let t; read: 'series-detail'">
|
||||
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid" #scrollingBlock>
|
||||
|
||||
@if (volume && series && libraryType !== null) {
|
||||
<div class="row mb-0 mb-xl-3 info-container">
|
||||
<div class="image-container col-5 col-sm-6 col-md-5 col-lg-5 col-xl-2 col-xxl-2 col-xxxl-2 d-none d-sm-block mb-3">
|
||||
|
||||
<app-image [styles]="{'object-fit': 'contain', 'background': 'none', 'max-height': '400px'}" [imageUrl]="coverImage"></app-image>
|
||||
@if (volume.pagesRead < volume.pages && hasReadingProgress) {
|
||||
<div class="progress-banner" ngbTooltip="{{(volume.pagesRead / volume.pages) * 100 | number:'1.0-1'}}%">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="volume.pagesRead" [max]="volume.pages" [showValue]="true"></ngb-progressbar>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-xl-10 col-lg-7 col-md-7 col-xs-8 col-sm-6">
|
||||
<h4 class="title mb-2">
|
||||
<a routerLink="/library/{{series.libraryId}}/series/{{series.id}}" class="dark-exempt btn-icon">{{series.name}}</a>
|
||||
</h4>
|
||||
<div class="subtitle mt-2 mb-2">
|
||||
<span>
|
||||
{{t('volume-num')}}
|
||||
<app-entity-title [libraryType]="libraryType!" [entity]="volume" [prioritizeTitleName]="true"></app-entity-title>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<app-metadata-detail-row [entity]="volumeCast"
|
||||
[ageRating]="maxAgeRating"
|
||||
[hasReadingProgress]="hasReadingProgress"
|
||||
[readingTimeEntity]="volume"
|
||||
[libraryType]="libraryType">
|
||||
</app-metadata-detail-row>
|
||||
|
||||
<!-- Rating goes here (after I implement support for rating individual issues -->
|
||||
<!-- @if (libraryType !== null && series) {-->
|
||||
<!-- <div class="mt-2 mb-2">-->
|
||||
<!-- <app-external-rating [seriesId]="series.id"-->
|
||||
<!-- [ratings]="[]"-->
|
||||
<!-- [userRating]="series.userRating"-->
|
||||
<!-- [hasUserRated]="series.hasUserRated"-->
|
||||
<!-- [libraryType]="libraryType">-->
|
||||
<!-- </app-external-rating>-->
|
||||
<!-- </div>-->
|
||||
<!-- }-->
|
||||
|
||||
<div class="mt-2 mb-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary-outline" (click)="readVolume()">
|
||||
<span>
|
||||
<i class="fa {{hasReadingProgress ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" display="dynamic" [attr.aria-label]="t('read-options-alt')">
|
||||
<button type="button" class="btn btn-primary-outline dropdown-toggle-split" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu" ngbDropdownMenu>
|
||||
<button ngbDropdownItem (click)="readVolume(true)">
|
||||
<span>
|
||||
<i class="fa fa-glasses" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue-incognito') : t('read-incognito')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@if (accountService.isAdmin$ | async) {
|
||||
<div class="col-auto ms-2">
|
||||
<button class="btn btn-secondary-outline" (click)="openEditModal()" [ngbTooltip]="t('edit-series-alt')">
|
||||
<span><i class="fa fa-pen" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="col-auto ms-2 d-none d-md-block">
|
||||
<app-download-button [download$]="download$" [entity]="volume" entityType="volume"></app-download-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 mb-3">
|
||||
<app-read-more [text]="volume.chapters[0].summary || ''"></app-read-more>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="row g-0">
|
||||
<div class="col-6">
|
||||
<span>{{t('writers-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="volumeCast.writers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.Writers, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span>{{t('cover-artists-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="volumeCast.coverArtists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.CoverArtist, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="carousel-tabs-container">
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs mb-2" (navChange)="onNavChange($event)">
|
||||
<li [ngbNavItem]="TabID.Chapters">
|
||||
<a ngbNavLink>
|
||||
{{utilityService.formatChapterName(libraryType!, false, false, true)}}
|
||||
<span class="badge rounded-pill text-bg-secondary">{{volume.chapters.length}}</span>
|
||||
</a>
|
||||
<ng-template ngbNavContent>
|
||||
@defer (when activeTabId === TabID.Chapters; prefetch on idle) {
|
||||
<virtual-scroller #scroll [items]="volume.chapters" [bufferAmount]="1" [parentScroll]="scrollingBlock" [childHeight]="1">
|
||||
<div class="card-container row g-0" #container>
|
||||
@for(item of scroll.viewPortItems; let idx = $index; track item.id + '_' + item.pagesRead) {
|
||||
<app-chapter-card class="col-auto mt-2 mb-2" [chapter]="item" [seriesId]="seriesId" [libraryId]="libraryId" [libraryType]="libraryType"></app-chapter-card>
|
||||
}
|
||||
</div>
|
||||
</virtual-scroller>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
@if (volume.chapters.length === 1 && readingLists.length > 0) {
|
||||
<li [ngbNavItem]="TabID.Related">
|
||||
<a ngbNavLink>{{t('related-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
@defer (when activeTabId === TabID.Related; prefetch on idle) {
|
||||
<app-related-tab [readingLists]="readingLists"></app-related-tab>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
}
|
||||
|
||||
@if (showDetailsTab) {
|
||||
<li [ngbNavItem]="TabID.Details">
|
||||
<a ngbNavLink>{{t('details-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
||||
<app-details-tab [metadata]="volumeCast" [genres]="genres" [tags]="tags"></app-details-tab>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Min height helps with scroll jerking when switching from chapter -> related/details -->
|
||||
<div [ngbNavOutlet]="nav" style="min-height: 300px"></div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
<app-loading [loading]="isLoading"></app-loading>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
23
UI/Web/src/app/volume-detail/volume-detail.component.scss
Normal file
23
UI/Web/src/app/volume-detail/volume-detail.component.scss
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
@use '../../series-detail-common';
|
||||
|
||||
.virtual-scroller, virtual-scroller {
|
||||
width: 100%;
|
||||
max-height: calc(var(--vh)*100 - 170px);
|
||||
}
|
||||
|
||||
// This is responsible for ensuring we scroll down and only tabs and companion bar is visible
|
||||
.main-container {
|
||||
// Height set dynamically by get ScrollingBlockHeight()
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
.card-container{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 158px);
|
||||
grid-gap: 0.5rem;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
|
||||
|
||||
461
UI/Web/src/app/volume-detail/volume-detail.component.ts
Normal file
461
UI/Web/src/app/volume-detail/volume-detail.component.ts
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
inject,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {AsyncPipe, DecimalPipe, DOCUMENT, NgStyle} from "@angular/common";
|
||||
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
|
||||
import {ImageService} from "../_services/image.service";
|
||||
import {SeriesService} from "../_services/series.service";
|
||||
import {LibraryService} from "../_services/library.service";
|
||||
import {ThemeService} from "../_services/theme.service";
|
||||
import {DownloadEvent, DownloadService} from "../shared/_services/download.service";
|
||||
import {BulkSelectionService} from "../cards/bulk-selection.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {ReaderService} from "../_services/reader.service";
|
||||
import {AccountService} from "../_services/account.service";
|
||||
import {
|
||||
NgbDropdown,
|
||||
NgbDropdownItem,
|
||||
NgbDropdownMenu,
|
||||
NgbDropdownToggle,
|
||||
NgbModal,
|
||||
NgbNav,
|
||||
NgbNavChangeEvent,
|
||||
NgbNavContent,
|
||||
NgbNavItem,
|
||||
NgbNavLink,
|
||||
NgbNavOutlet,
|
||||
NgbProgressbar,
|
||||
NgbTooltip
|
||||
} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {FilterUtilitiesService} from "../shared/_services/filter-utilities.service";
|
||||
import {Chapter} from "../_models/chapter";
|
||||
import {Series} from "../_models/series";
|
||||
import {LibraryType} from "../_models/library/library";
|
||||
import {forkJoin, map, Observable, shareReplay, tap} from "rxjs";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {EditChapterModalComponent} from "../_single-module/edit-chapter-modal/edit-chapter-modal.component";
|
||||
import {FilterComparison} from "../_models/metadata/v2/filter-comparison";
|
||||
import {FilterField} from '../_models/metadata/v2/filter-field';
|
||||
import {AgeRating} from '../_models/metadata/age-rating';
|
||||
import {Volume} from "../_models/volume";
|
||||
import {VolumeService} from "../_services/volume.service";
|
||||
import {LoadingComponent} from "../shared/loading/loading.component";
|
||||
import {DetailsTabComponent} from "../_single-module/details-tab/details-tab.component";
|
||||
import {ReadMoreComponent} from "../shared/read-more/read-more.component";
|
||||
import {Person} from "../_models/metadata/person";
|
||||
import {hasAnyCast, IHasCast} from "../_models/common/i-has-cast";
|
||||
import {ReadTimePipe} from "../_pipes/read-time.pipe";
|
||||
import {AgeRatingPipe} from "../_pipes/age-rating.pipe";
|
||||
import {EntityTitleComponent} from "../cards/entity-title/entity-title.component";
|
||||
import {ImageComponent} from "../shared/image/image.component";
|
||||
import {CardItemComponent} from "../cards/card-item/card-item.component";
|
||||
import {VirtualScrollerModule} from "@iharbeck/ngx-virtual-scroller";
|
||||
import {Action, ActionFactoryService, ActionItem} from "../_services/action-factory.service";
|
||||
import {UtilityService} from "../shared/_services/utility.service";
|
||||
import {ChapterCardComponent} from "../cards/chapter-card/chapter-card.component";
|
||||
import {DefaultValuePipe} from "../_pipes/default-value.pipe";
|
||||
import {
|
||||
EditVolumeModalCloseResult,
|
||||
EditVolumeModalComponent
|
||||
} from "../_single-module/edit-volume-modal/edit-volume-modal.component";
|
||||
import {Genre} from "../_models/metadata/genre";
|
||||
import {Tag} from "../_models/tag";
|
||||
import {RelatedTabComponent} from "../_single-modules/related-tab/related-tab.component";
|
||||
import {ReadingList} from "../_models/reading-list";
|
||||
import {ReadingListService} from "../_services/reading-list.service";
|
||||
import {AgeRatingImageComponent} from "../_single-modules/age-rating-image/age-rating-image.component";
|
||||
import {CompactNumberPipe} from "../_pipes/compact-number.pipe";
|
||||
import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.component";
|
||||
import {
|
||||
MetadataDetailRowComponent
|
||||
} from "../series-detail/_components/metadata-detail-row/metadata-detail-row.component";
|
||||
import {DownloadButtonComponent} from "../series-detail/_components/download-button/download-button.component";
|
||||
|
||||
enum TabID {
|
||||
|
||||
Chapters = 'chapters-tab',
|
||||
Related = 'related-tab',
|
||||
Reviews = 'reviews-tab', // Only applicable for books
|
||||
Details = 'details-tab',
|
||||
}
|
||||
|
||||
interface VolumeCast extends IHasCast {
|
||||
characterLocked: boolean;
|
||||
characters: Array<Person>;
|
||||
coloristLocked: boolean;
|
||||
colorists: Array<Person>;
|
||||
coverArtistLocked: boolean;
|
||||
coverArtists: Array<Person>;
|
||||
editorLocked: boolean;
|
||||
editors: Array<Person>;
|
||||
imprintLocked: boolean;
|
||||
imprints: Array<Person>;
|
||||
inkerLocked: boolean;
|
||||
inkers: Array<Person>;
|
||||
languageLocked: boolean;
|
||||
lettererLocked: boolean;
|
||||
letterers: Array<Person>;
|
||||
locationLocked: boolean;
|
||||
locations: Array<Person>;
|
||||
pencillerLocked: boolean;
|
||||
pencillers: Array<Person>;
|
||||
publisherLocked: boolean;
|
||||
publishers: Array<Person>;
|
||||
teamLocked: boolean;
|
||||
teams: Array<Person>;
|
||||
translatorLocked: boolean;
|
||||
translators: Array<Person>;
|
||||
writerLocked: boolean;
|
||||
writers: Array<Person>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-volume-detail',
|
||||
standalone: true,
|
||||
imports: [
|
||||
LoadingComponent,
|
||||
NgbNavOutlet,
|
||||
DetailsTabComponent,
|
||||
NgbNavItem,
|
||||
NgbNavLink,
|
||||
NgbNavContent,
|
||||
NgbNav,
|
||||
ReadMoreComponent,
|
||||
AsyncPipe,
|
||||
NgbDropdownItem,
|
||||
NgbDropdownMenu,
|
||||
NgbDropdown,
|
||||
NgbDropdownToggle,
|
||||
ReadTimePipe,
|
||||
AgeRatingPipe,
|
||||
EntityTitleComponent,
|
||||
RouterLink,
|
||||
NgbProgressbar,
|
||||
DecimalPipe,
|
||||
NgbTooltip,
|
||||
ImageComponent,
|
||||
NgStyle,
|
||||
TranslocoDirective,
|
||||
CardItemComponent,
|
||||
VirtualScrollerModule,
|
||||
ChapterCardComponent,
|
||||
DefaultValuePipe,
|
||||
RelatedTabComponent,
|
||||
AgeRatingImageComponent,
|
||||
CompactNumberPipe,
|
||||
BadgeExpanderComponent,
|
||||
MetadataDetailRowComponent,
|
||||
DownloadButtonComponent
|
||||
],
|
||||
templateUrl: './volume-detail.component.html',
|
||||
styleUrl: './volume-detail.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class VolumeDetailComponent implements OnInit {
|
||||
private readonly document = inject(DOCUMENT);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
protected readonly imageService = inject(ImageService);
|
||||
private readonly volumeService = inject(VolumeService);
|
||||
private readonly seriesService = inject(SeriesService);
|
||||
private readonly libraryService = inject(LibraryService);
|
||||
private readonly themeService = inject(ThemeService);
|
||||
private readonly downloadService = inject(DownloadService);
|
||||
protected readonly bulkSelectionService = inject(BulkSelectionService);
|
||||
private readonly readerService = inject(ReaderService);
|
||||
protected readonly accountService = inject(AccountService);
|
||||
private readonly modalService = inject(NgbModal);
|
||||
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly actionFactoryService = inject(ActionFactoryService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
private readonly readingListService = inject(ReadingListService);
|
||||
|
||||
|
||||
protected readonly AgeRating = AgeRating;
|
||||
protected readonly TabID = TabID;
|
||||
protected readonly FilterField = FilterField;
|
||||
|
||||
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
|
||||
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
|
||||
|
||||
isLoading: boolean = true;
|
||||
coverImage: string = '';
|
||||
volumeId: number = 0;
|
||||
seriesId: number = 0;
|
||||
libraryId: number = 0;
|
||||
volume: Volume | null = null;
|
||||
series: Series | null = null;
|
||||
libraryType: LibraryType | null = null;
|
||||
hasReadingProgress = false;
|
||||
activeTabId = TabID.Chapters;
|
||||
readingLists: ReadingList[] = [];
|
||||
|
||||
volumeActions: Array<ActionItem<Chapter>> = this.actionFactoryService.getVolumeActions(this.handleVolumeAction.bind(this));
|
||||
|
||||
/**
|
||||
* This is the download we get from download service.
|
||||
*/
|
||||
download$: Observable<DownloadEvent | null> | null = null;
|
||||
showDetailsTab: boolean = true;
|
||||
|
||||
maxAgeRating: AgeRating = AgeRating.Unknown;
|
||||
volumeCast: VolumeCast = {
|
||||
characterLocked: false,
|
||||
characters: [],
|
||||
coloristLocked: false,
|
||||
colorists: [],
|
||||
coverArtistLocked: false,
|
||||
coverArtists: [],
|
||||
editorLocked: false,
|
||||
editors: [],
|
||||
imprintLocked: false,
|
||||
imprints: [],
|
||||
inkerLocked: false,
|
||||
inkers: [],
|
||||
languageLocked: false,
|
||||
lettererLocked: false,
|
||||
letterers: [],
|
||||
locationLocked: false,
|
||||
locations: [],
|
||||
pencillerLocked: false,
|
||||
pencillers: [],
|
||||
publisherLocked: false,
|
||||
publishers: [],
|
||||
teamLocked: false,
|
||||
teams: [],
|
||||
translatorLocked: false,
|
||||
translators: [],
|
||||
writerLocked: false,
|
||||
writers: []
|
||||
};
|
||||
tags: Array<Tag> = [];
|
||||
genres: Array<Genre> = [];
|
||||
|
||||
|
||||
|
||||
get ScrollingBlockHeight() {
|
||||
if (this.scrollingBlock === undefined) return 'calc(var(--vh)*100)';
|
||||
const navbar = this.document.querySelector('.navbar') as HTMLElement;
|
||||
if (navbar === null) return 'calc(var(--vh)*100)';
|
||||
|
||||
const companionHeight = this.companionBar?.nativeElement.offsetHeight || 0;
|
||||
const navbarHeight = navbar.offsetHeight;
|
||||
const totalHeight = companionHeight + navbarHeight + 21; //21px to account for padding
|
||||
return 'calc(var(--vh)*100 - ' + totalHeight + 'px)';
|
||||
}
|
||||
|
||||
get UseBookLogic() {
|
||||
return this.libraryType === LibraryType.Book || this.libraryType === LibraryType.LightNovel;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const seriesId = this.route.snapshot.paramMap.get('seriesId');
|
||||
const libraryId = this.route.snapshot.paramMap.get('libraryId');
|
||||
const volumeId = this.route.snapshot.paramMap.get('volumeId');
|
||||
if (seriesId === null || libraryId === null || volumeId === null) {
|
||||
this.router.navigateByUrl('/home');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.seriesId = parseInt(seriesId, 10);
|
||||
this.volumeId = parseInt(volumeId, 10);
|
||||
this.libraryId = parseInt(libraryId, 10);
|
||||
|
||||
this.coverImage = this.imageService.getVolumeCoverImage(this.volumeId);
|
||||
|
||||
|
||||
|
||||
forkJoin({
|
||||
series: this.seriesService.getSeries(this.seriesId),
|
||||
volume: this.volumeService.getVolumeMetadata(this.volumeId),
|
||||
libraryType: this.libraryService.getLibraryType(this.libraryId)
|
||||
}).subscribe(results => {
|
||||
|
||||
if (results.volume === null) {
|
||||
this.router.navigateByUrl('/home');
|
||||
return;
|
||||
}
|
||||
|
||||
this.series = results.series;
|
||||
this.volume = results.volume;
|
||||
this.libraryType = results.libraryType;
|
||||
|
||||
this.themeService.setColorScape(this.volume!.primaryColor, this.volume!.secondaryColor);
|
||||
|
||||
// Set up the download in progress
|
||||
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
|
||||
return this.downloadService.mapToEntityType(events, this.volume!);
|
||||
}));
|
||||
|
||||
this.route.fragment.pipe(tap(frag => {
|
||||
if (frag !== null && this.activeTabId !== (frag as TabID)) {
|
||||
this.activeTabId = frag as TabID;
|
||||
this.updateUrl(this.activeTabId);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}), takeUntilDestroyed(this.destroyRef)).subscribe();
|
||||
|
||||
if (this.volume.chapters.length === 1) {
|
||||
this.readingListService.getReadingListsForChapter(this.volume.chapters[0].id).subscribe(lists => {
|
||||
this.readingLists = lists;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate all the writes/artists for all chapters
|
||||
this.volumeCast.writers = this.volume.chapters
|
||||
.flatMap(c => c.writers) // Flatten the array of writers from all chapters
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name) // Check for distinct names
|
||||
);
|
||||
|
||||
this.volumeCast.coverArtists = this.volume.chapters
|
||||
.flatMap(c => c.coverArtists)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
|
||||
this.volumeCast.characters = this.volume.chapters
|
||||
.flatMap(c => c.characters)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
this.volumeCast.colorists = this.volume.chapters
|
||||
.flatMap(c => c.colorists)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
this.volumeCast.editors = this.volume.chapters
|
||||
.flatMap(c => c.editors)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
this.volumeCast.imprints = this.volume.chapters
|
||||
.flatMap(c => c.imprints)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
this.volumeCast.inkers = this.volume.chapters
|
||||
.flatMap(c => c.inkers)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
this.volumeCast.letterers = this.volume.chapters
|
||||
.flatMap(c => c.letterers)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
this.volumeCast.locations = this.volume.chapters
|
||||
.flatMap(c => c.locations)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
|
||||
this.volumeCast.teams = this.volume.chapters
|
||||
.flatMap(c => c.teams)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
|
||||
this.volumeCast.translators = this.volume.chapters
|
||||
.flatMap(c => c.translators)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
|
||||
this.volumeCast.publishers = this.volume.chapters
|
||||
.flatMap(c => c.publishers)
|
||||
.filter((person, index, self) =>
|
||||
index === self.findIndex(w => w.name === person.name)
|
||||
);
|
||||
|
||||
this.genres = this.volume.chapters
|
||||
.flatMap(c => c.genres)
|
||||
.filter((tag, index, self) =>
|
||||
index === self.findIndex(w => w.title === tag.title)
|
||||
);
|
||||
|
||||
this.tags = this.volume.chapters
|
||||
.flatMap(c => c.tags)
|
||||
.filter((tag, index, self) =>
|
||||
index === self.findIndex(w => w.title === tag.title)
|
||||
);
|
||||
|
||||
this.maxAgeRating = Math.max(
|
||||
...this.volume.chapters
|
||||
.flatMap(c => c.ageRating)
|
||||
);
|
||||
|
||||
this.showDetailsTab = hasAnyCast(this.volumeCast) || (this.genres || []).length > 0 || (this.tags || []).length > 0;
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
readChapter(chapter: Chapter, incognitoMode: boolean = false) {
|
||||
if (this.bulkSelectionService.hasSelections()) return;
|
||||
|
||||
this.readerService.readChapter(this.libraryId, this.seriesId, chapter, incognitoMode);
|
||||
}
|
||||
|
||||
readVolume(incognitoMode: boolean = false) {
|
||||
if (!this.volume) return;
|
||||
|
||||
this.readerService.readVolume(this.libraryId, this.seriesId, this.volume, incognitoMode);
|
||||
}
|
||||
|
||||
openEditModal() {
|
||||
const ref = this.modalService.open(EditVolumeModalComponent, { size: 'xl' });
|
||||
ref.componentInstance.volume = this.volume;
|
||||
ref.componentInstance.libraryType = this.libraryType;
|
||||
ref.componentInstance.libraryId = this.libraryId;
|
||||
ref.componentInstance.seriesId = this.series!.id;
|
||||
|
||||
ref.closed.subscribe((res: EditVolumeModalCloseResult) => {
|
||||
// TODO
|
||||
});
|
||||
}
|
||||
|
||||
onNavChange(event: NgbNavChangeEvent) {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.updateUrl(event.nextId);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateUrl(activeTab: TabID) {
|
||||
const newUrl = `${this.router.url.split('#')[0]}#${activeTab}`;
|
||||
this.router.navigateByUrl(newUrl, { onSameUrlNavigation: 'ignore' });
|
||||
}
|
||||
|
||||
openPerson(field: FilterField, value: number) {
|
||||
this.filterUtilityService.applyFilter(['all-series'], field, FilterComparison.Equal, `${value}`).subscribe();
|
||||
}
|
||||
|
||||
handleVolumeAction(action: ActionItem<Volume>) {
|
||||
// TODO: Implement actionables
|
||||
switch (action.action) {
|
||||
case Action.Delete:
|
||||
break;
|
||||
case Action.MarkAsRead:
|
||||
break;
|
||||
case Action.MarkAsUnread:
|
||||
break;
|
||||
case Action.MarkAsRead:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue