Reader Polish (#2465)
Co-authored-by: Andre Smith <Hobogrammer@users.noreply.github.com>
This commit is contained in:
parent
9fdaf5f99f
commit
e489d2404a
24 changed files with 156 additions and 120 deletions
|
|
@ -17,7 +17,7 @@ export enum RelationKind {
|
|||
Edition = 13
|
||||
}
|
||||
|
||||
export const RelationKinds = [
|
||||
const RelationKindsUnsorted = [
|
||||
{text: 'Prequel', value: RelationKind.Prequel},
|
||||
{text: 'Sequel', value: RelationKind.Sequel},
|
||||
{text: 'Spin Off', value: RelationKind.SpinOff},
|
||||
|
|
@ -31,3 +31,5 @@ export const RelationKinds = [
|
|||
{text: 'Doujinshi', value: RelationKind.Doujinshi},
|
||||
{text: 'Other', value: RelationKind.Other},
|
||||
];
|
||||
|
||||
export const RelationKinds = RelationKindsUnsorted.slice().sort((a, b) => a.text.localeCompare(b.text));
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export class BookLineOverlayComponent implements OnInit {
|
|||
@Input({required: true}) pageNumber: number = 0;
|
||||
@Input({required: true}) parent: ElementRef | undefined;
|
||||
@Output() refreshToC: EventEmitter<void> = new EventEmitter();
|
||||
@Output() isOpen: EventEmitter<boolean> = new EventEmitter(false);
|
||||
|
||||
xPath: string = '';
|
||||
selectedText: string = '';
|
||||
|
|
@ -84,6 +85,8 @@ export class BookLineOverlayComponent implements OnInit {
|
|||
if (!event.target) return;
|
||||
|
||||
if ((!selection || selection.toString().trim() === '' || selection.toString().trim() === this.selectedText)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
|
@ -96,6 +99,7 @@ export class BookLineOverlayComponent implements OnInit {
|
|||
this.xPath = '//' + this.xPath;
|
||||
}
|
||||
|
||||
this.isOpen.emit(true);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
|
@ -137,6 +141,7 @@ export class BookLineOverlayComponent implements OnInit {
|
|||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
}
|
||||
this.isOpen.emit(false);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
<ng-container *transloco="let t; read: 'book-reader'">
|
||||
<div class="fixed-top" #stickyTop>
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">{{t('skip-header')}}</a>
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: true}"></ng-container>
|
||||
<app-book-line-overlay [parent]="bookContainerElemRef" *ngIf="page !== undefined"
|
||||
[libraryId]="libraryId"
|
||||
[volumeId]="volumeId"
|
||||
[chapterId]="chapterId"
|
||||
[seriesId]="seriesId"
|
||||
[pageNumber]="pageNum"
|
||||
(isOpen)="updateLineOverlayOpen($event)"
|
||||
(refreshToC)="refreshPersonalToC()">
|
||||
</app-book-line-overlay>
|
||||
<app-drawer #commentDrawer="drawer" [(isOpen)]="drawerOpen" [options]="{topOffset: topOffset}">
|
||||
|
|
@ -18,11 +19,6 @@
|
|||
<span style="font-size: 14px; color: var(--primary-color)" tabindex="0" role="button" (click)="closeReader()">{{t('close-reader')}}</span>
|
||||
</div>
|
||||
<div subheader>
|
||||
<!-- <div class="g-0 text-center" *ngIf="!isLoading">-->
|
||||
<!-- <span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">-->
|
||||
<!-- (<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-mode-label')}}</span>)</span>-->
|
||||
<!-- <span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>-->
|
||||
<!-- </div>-->
|
||||
<div class="pagination-cont">
|
||||
<ng-container *ngIf="layoutMode !== BookPageLayoutMode.Default">
|
||||
<div class="virt-pagination-cont">
|
||||
|
|
@ -129,13 +125,14 @@
|
|||
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)"
|
||||
(click)="$event.stopPropagation();"
|
||||
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #actionBar>
|
||||
<ng-template #actionBar let-isTop>
|
||||
<div class="action-bar row g-0 justify-content-between" *ngIf="!immersiveMode || drawerOpen || actionBarVisible">
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsPrevDisabled : IsNextDisabled"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? t('previous') : t('next')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{readingDirection === ReadingDirection.RightToLeft ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
|
|
@ -146,20 +143,20 @@
|
|||
<button class="btn btn-secondary col-2 col-xs-1" (click)="toggleDrawer()">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i></button>
|
||||
<div class="book-title col-2 d-none d-sm-block">
|
||||
<ng-container *ngIf="isLoading; else showTitle">
|
||||
@if(isLoading) {
|
||||
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
||||
<span class="visually-hidden">{{t('loading-book')}}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #showTitle>
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
|
||||
} @else {
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
|
||||
(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-mode-label')}}</span>)</span>
|
||||
<span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>
|
||||
</ng-template>
|
||||
<span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>
|
||||
}
|
||||
</div>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="closeReader()"><i class="fa fa-times-circle" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsNextDisabled : IsPrevDisabled"
|
||||
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? t('next') : t('previous')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -185,6 +185,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* Belongs to drawer component
|
||||
*/
|
||||
drawerOpen = false;
|
||||
/**
|
||||
* If the word/line overlay is open
|
||||
*/
|
||||
isLineOverlayOpen = false;
|
||||
/**
|
||||
* If the action bar is visible
|
||||
*/
|
||||
|
|
@ -1630,20 +1634,21 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
// Responsible for handling pagination only
|
||||
handleContainerClick(event: MouseEvent) {
|
||||
|
||||
if (this.drawerOpen || ['action-bar', 'offcanvas-backdrop'].some(className => (event.target as Element).classList.contains(className))) {
|
||||
if (this.drawerOpen || this.isLineOverlayOpen || ['action-bar', 'offcanvas-backdrop'].some(className => (event.target as Element).classList.contains(className))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isCursorOverLeftPaginationArea(event)) {
|
||||
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD);
|
||||
} else if (this.isCursorOverRightPaginationArea(event)) {
|
||||
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)
|
||||
} else {
|
||||
this.toggleMenu(event);
|
||||
if (this.clickToPaginate) {
|
||||
if (this.isCursorOverLeftPaginationArea(event)) {
|
||||
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD);
|
||||
} else if (this.isCursorOverRightPaginationArea(event)) {
|
||||
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)
|
||||
}
|
||||
}
|
||||
|
||||
this.toggleMenu(event);
|
||||
}
|
||||
|
||||
handleReaderClick(event: MouseEvent) {
|
||||
|
|
@ -1706,4 +1711,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
refreshPersonalToC() {
|
||||
this.refreshPToC.emit();
|
||||
}
|
||||
|
||||
updateLineOverlayOpen(isOpen: boolean) {
|
||||
// HACK: This hack allows the boolean to be changed to false so that the pagination doesn't trigger and move us to the next page when
|
||||
// the book overlay is just closing
|
||||
setTimeout(() => {
|
||||
this.isLineOverlayOpen = isOpen;
|
||||
this.cdRef.markForCheck();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@
|
|||
<div class="col-md-12">
|
||||
<div class="mb-3">
|
||||
<label for="genres" class="form-label">{{t('genres-label')}}</label>
|
||||
<app-typeahead (selectedData)="updateGenres($event)" [settings]="genreSettings"
|
||||
<app-typeahead (selectedData)="updateGenres($event);metadata.genresLocked = true" [settings]="genreSettings"
|
||||
[(locked)]="metadata.genresLocked" (onUnlock)="metadata.genresLocked = false"
|
||||
(newItemAdded)="metadata.genresLocked = true" (selectedData)="metadata.genresLocked = true">
|
||||
(newItemAdded)="metadata.genresLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
|
|
@ -142,9 +142,9 @@
|
|||
<div class="col-lg-4 col-md-12 pe-2">
|
||||
<div class="mb-3">
|
||||
<label for="language" class="form-label">{{t('language-label')}}</label>
|
||||
<app-typeahead (selectedData)="updateLanguage($event)" [settings]="languageSettings"
|
||||
<app-typeahead (selectedData)="updateLanguage($event);metadata.languageLocked = true;" [settings]="languageSettings"
|
||||
[(locked)]="metadata.languageLocked" (onUnlock)="metadata.languageLocked = false"
|
||||
(newItemAdded)="metadata.languageLocked = true" (selectedData)="metadata.languageLocked = true">
|
||||
(newItemAdded)="metadata.languageLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
<label for="age-rating" class="form-label">{{t('age-rating-label')}}</label>
|
||||
<div class="input-group {{metadata.ageRatingLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'ageRatingLocked' }"></ng-container>
|
||||
<select class="form-select"id="age-rating" formControlName="ageRating">
|
||||
<select class="form-select" id="age-rating" formControlName="ageRating">
|
||||
<option *ngFor="let opt of ageRatings" [value]="opt.value">{{opt.title | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
<label for="publication-status" class="form-label">{{t('publication-status-label')}}</label>
|
||||
<div class="input-group {{metadata.publicationStatusLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'publicationStatusLocked' }"></ng-container>
|
||||
<select class="form-select"id="publication-status" formControlName="publicationStatus">
|
||||
<select class="form-select" id="publication-status" formControlName="publicationStatus">
|
||||
<option *ngFor="let opt of publicationStatuses" [value]="opt.value">{{opt.title | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -186,9 +186,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="writer" class="form-label">{{t('writer-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Writer);metadata.writerLocked = true" [settings]="getPersonsSettings(PersonRole.Writer)"
|
||||
[(locked)]="metadata.writerLocked" (onUnlock)="metadata.writerLocked = false"
|
||||
(newItemAdded)="metadata.writerLocked = true" (selectedData)="metadata.writerLocked = true">
|
||||
(newItemAdded)="metadata.writerLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -201,9 +201,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="cover-artist" class="form-label">{{t('cover-artist-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.CoverArtist);metadata.coverArtistLocked = true" [settings]="getPersonsSettings(PersonRole.CoverArtist)"
|
||||
[(locked)]="metadata.coverArtistLocked" (onUnlock)="metadata.coverArtistLocked = false"
|
||||
(newItemAdded)="metadata.coverArtistLocked = true" (selectedData)="metadata.coverArtistLocked = true">
|
||||
(newItemAdded)="metadata.coverArtistLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -218,9 +218,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="publisher" class="form-label">{{t('publisher-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Publisher);metadata.publisherLocked = true" [settings]="getPersonsSettings(PersonRole.Publisher)"
|
||||
[(locked)]="metadata.publisherLocked" (onUnlock)="metadata.publisherLocked = false"
|
||||
(newItemAdded)="metadata.publisherLocked = true" (selectedData)="metadata.publisherLocked = true">
|
||||
(newItemAdded)="metadata.publisherLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -233,9 +233,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="penciller" class="form-label">{{t('penciller-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Penciller);metadata.pencillerLocked = true" [settings]="getPersonsSettings(PersonRole.Penciller)"
|
||||
[(locked)]="metadata.pencillerLocked" (onUnlock)="metadata.pencillerLocked = false"
|
||||
(newItemAdded)="metadata.pencillerLocked = true" (selectedData)="metadata.pencillerLocked = true">
|
||||
(newItemAdded)="metadata.pencillerLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -250,9 +250,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="letterer" class="form-label">{{t('letterer-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Letterer);metadata.lettererLocked = true" [settings]="getPersonsSettings(PersonRole.Letterer)"
|
||||
[(locked)]="metadata.lettererLocked" (onUnlock)="metadata.lettererLocked = false"
|
||||
(newItemAdded)="metadata.lettererLocked = true" (selectedData)="metadata.lettererLocked = true">
|
||||
(newItemAdded)="metadata.lettererLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -265,9 +265,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="inker" class="form-label">{{t('inker-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Inker);metadata.inkerLocked = true" [settings]="getPersonsSettings(PersonRole.Inker)"
|
||||
[(locked)]="metadata.inkerLocked" (onUnlock)="metadata.inkerLocked = false"
|
||||
(newItemAdded)="metadata.inkerLocked = true" (selectedData)="metadata.inkerLocked = true">
|
||||
(newItemAdded)="metadata.inkerLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -283,9 +283,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="editor" class="form-label">{{t('editor-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Editor);metadata.editorLocked = true" [settings]="getPersonsSettings(PersonRole.Editor)"
|
||||
[(locked)]="metadata.editorLocked" (onUnlock)="metadata.editorLocked = false"
|
||||
(newItemAdded)="metadata.editorLocked = true" (selectedData)="metadata.editorLocked = true">
|
||||
(newItemAdded)="metadata.editorLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -298,9 +298,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="colorist" class="form-label">{{t('colorist-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Colorist);metadata.coloristLocked = true" [settings]="getPersonsSettings(PersonRole.Colorist)"
|
||||
[(locked)]="metadata.coloristLocked" (onUnlock)="metadata.coloristLocked = false"
|
||||
(newItemAdded)="metadata.coloristLocked = true" (selectedData)="metadata.coloristLocked = true">
|
||||
(newItemAdded)="metadata.coloristLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -316,9 +316,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="character" class="form-label">{{t('character-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Character);metadata.characterLocked = true" [settings]="getPersonsSettings(PersonRole.Character)"
|
||||
[(locked)]="metadata.characterLocked" (onUnlock)="metadata.characterLocked = false"
|
||||
(newItemAdded)="metadata.characterLocked = true" (selectedData)="metadata.characterLocked = true">
|
||||
(newItemAdded)="metadata.characterLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -331,9 +331,9 @@
|
|||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="translator" class="form-label">{{t('translator-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)"
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Translator);metadata.translatorLocked = true;" [settings]="getPersonsSettings(PersonRole.Translator)"
|
||||
[(locked)]="metadata.translatorLocked" (onUnlock)="metadata.translatorLocked = false"
|
||||
(newItemAdded)="metadata.translatorLocked = true" (selectedData)="metadata.translatorLocked = true">
|
||||
(newItemAdded)="metadata.translatorLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,15 @@ interface RelationControl {
|
|||
})
|
||||
export class EditSeriesRelationComponent implements OnInit {
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly seriesService = inject(SeriesService);
|
||||
private readonly utilityService = inject(UtilityService);
|
||||
private readonly libraryService = inject(LibraryService);
|
||||
private readonly searchService = inject(SearchService);
|
||||
public readonly imageService = inject(ImageService);
|
||||
protected readonly RelationKind = RelationKind;
|
||||
|
||||
@Input({required: true}) series!: Series;
|
||||
/**
|
||||
* This will tell the component to save based on its internal state
|
||||
|
|
@ -60,16 +69,6 @@ export class EditSeriesRelationComponent implements OnInit {
|
|||
libraryNames: {[key:number]: string} = {};
|
||||
|
||||
focusTypeahead = new EventEmitter();
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
get RelationKind() {
|
||||
return RelationKind;
|
||||
}
|
||||
|
||||
|
||||
constructor(private seriesService: SeriesService, private utilityService: UtilityService,
|
||||
public imageService: ImageService, private libraryService: LibraryService, private searchService: SearchService,
|
||||
private readonly cdRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seriesService.getRelatedForSeries(this.series.id).subscribe(async relations => {
|
||||
|
|
|
|||
|
|
@ -76,9 +76,8 @@ export class DoubleRendererComponent implements OnInit, ImageRenderer {
|
|||
shouldRenderDouble$!: Observable<boolean>;
|
||||
|
||||
|
||||
get ReaderMode() {return ReaderMode;}
|
||||
get LayoutMode() {return LayoutMode;}
|
||||
|
||||
protected readonly ReaderMode = ReaderMode;
|
||||
protected readonly LayoutMode = LayoutMode;
|
||||
|
||||
|
||||
constructor(private readonly cdRef: ChangeDetectorRef, public mangaReaderService: ManagaReaderService,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<div>
|
||||
<div style="font-weight: bold;">{{title}} <span class="clickable" *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-alt')">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-title')}}</span>)</span></div>
|
||||
<div class="subtitle">
|
||||
{{subtitle}} <span *ngIf="totalSeriesPages > 0">{{t('series-progress', {percentage: (((totalSeriesPagesRead + pageNum) / totalSeriesPages) | percent)}) }}</span>
|
||||
{{subtitle}} <span *ngIf="totalSeriesPages > 0">{{t('series-progress', {percentage: (Math.min(1, ((totalSeriesPagesRead + pageNum) / totalSeriesPages)) | percent)}) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<app-loading [loading]="isLoading" [absolute]="true"></app-loading>
|
||||
<app-loading [loading]="isLoading || !(currentImage$ | async)?.complete" [absolute]="true"></app-loading>
|
||||
<div class="reading-area"
|
||||
ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)"
|
||||
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {DOCUMENT, NgStyle, NgIf, NgFor, NgSwitch, NgSwitchCase, PercentPipe, NgClass} from '@angular/common';
|
||||
import {DOCUMENT, NgStyle, NgIf, NgFor, NgSwitch, NgSwitchCase, PercentPipe, NgClass, AsyncPipe} from '@angular/common';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
|
|
@ -68,6 +68,7 @@ import { InfiniteScrollerComponent } from '../infinite-scroller/infinite-scrolle
|
|||
import { SwipeDirective } from '../../../ng-swipe/ng-swipe.directive';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
import {shareReplay} from "rxjs/operators";
|
||||
|
||||
|
||||
const PREFETCH_PAGES = 10;
|
||||
|
|
@ -124,7 +125,7 @@ enum KeyDirection {
|
|||
imports: [NgStyle, NgIf, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent,
|
||||
DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent,
|
||||
NgxSliderModule, ReactiveFormsModule, NgFor, NgSwitch, NgSwitchCase, FittingIconPipe, ReaderModeIconPipe,
|
||||
FullscreenIconPipe, TranslocoDirective, NgbProgressbar, PercentPipe, NgClass]
|
||||
FullscreenIconPipe, TranslocoDirective, NgbProgressbar, PercentPipe, NgClass, AsyncPipe]
|
||||
})
|
||||
export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
|
|
@ -158,6 +159,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
protected readonly LayoutMode = LayoutMode;
|
||||
protected readonly ReadingDirection = ReadingDirection;
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
protected readonly Math = Math;
|
||||
|
||||
libraryId!: number;
|
||||
seriesId!: number;
|
||||
|
|
@ -400,7 +402,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
// Renderer interaction
|
||||
readerSettings$!: Observable<ReaderSetting>;
|
||||
private currentImage: Subject<HTMLImageElement | null> = new ReplaySubject(1);
|
||||
currentImage$: Observable<HTMLImageElement | null> = this.currentImage.asObservable();
|
||||
currentImage$: Observable<HTMLImageElement | null> = this.currentImage.asObservable().pipe(
|
||||
shareReplay({refCount: true, bufferSize: 2})
|
||||
);
|
||||
|
||||
private pageNumSubject: Subject<{pageNum: number, maxPages: number}> = new ReplaySubject();
|
||||
pageNum$: Observable<{pageNum: number, maxPages: number}> = this.pageNumSubject.asObservable();
|
||||
|
|
@ -743,18 +747,34 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* @param pageNum Page Number to load
|
||||
* @param forceNew Forces to fetch a new image
|
||||
* @param chapterId ChapterId to fetch page from. Defaults to current chapterId. Not used when in bookmark mode
|
||||
* @returns
|
||||
* @returns HTMLImageElement | undefined
|
||||
*/
|
||||
getPage(pageNum: number, chapterId: number = this.chapterId, forceNew: boolean = false) {
|
||||
|
||||
let img;
|
||||
let img: HTMLImageElement | undefined;
|
||||
if (this.bookmarkMode) img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum);
|
||||
else img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum
|
||||
&& (this.readerService.imageUrlToChapterId(img.src) == chapterId || this.readerService.imageUrlToChapterId(img.src) === -1)
|
||||
);
|
||||
|
||||
//console.log('Requesting page ', pageNum, ' found page: ', img, ' and app is requesting new image? ', forceNew);
|
||||
if (!img || forceNew) {
|
||||
img = new Image();
|
||||
img.src = this.getPageUrl(pageNum, chapterId);
|
||||
img.onload = (evt) => {
|
||||
this.currentImage.next(img!);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
// img.onerror = (evt) => {
|
||||
// const event = evt as Event;
|
||||
// const page = this.readerService.imageUrlToPageNum((event.target as HTMLImageElement).src);
|
||||
// console.error('Image failed to load: ', page);
|
||||
// (event.target as HTMLImageElement).onerror = null;
|
||||
// const newSrc = this.getPageUrl(pageNum, chapterId) + '#' + new Date().getTime();
|
||||
// console.log('requesting page ', page, ' with url: ', newSrc);
|
||||
// (event.target as HTMLImageElement).src = newSrc;
|
||||
// this.cdRef.markForCheck();
|
||||
// }
|
||||
}
|
||||
|
||||
return img;
|
||||
|
|
@ -1224,6 +1244,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
*/
|
||||
setCanvasImage() {
|
||||
if (this.cachedImages === undefined) return;
|
||||
|
||||
this.canvasImage = this.getPage(this.pageNum, this.chapterId, this.layoutMode !== LayoutMode.Single);
|
||||
if (!this.canvasImage.complete) {
|
||||
this.canvasImage.addEventListener('load', () => {
|
||||
|
|
@ -1346,19 +1367,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
const index = (numOffset % this.cachedImages.length + this.cachedImages.length) % this.cachedImages.length;
|
||||
const cachedImagePageNum = this.readerService.imageUrlToPageNum(this.cachedImages[index].src);
|
||||
if (cachedImagePageNum !== numOffset) {
|
||||
this.cachedImages[index] = new Image();
|
||||
this.cachedImages[index].src = this.getPageUrl(numOffset);
|
||||
this.cachedImages[index].onload = (evt) => {
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
this.cachedImages[index] = this.getPage(numOffset, this.chapterId);
|
||||
}
|
||||
}
|
||||
|
||||
//const pages = this.cachedImages.map(img => [this.readerService.imageUrlToChapterId(img.src), this.readerService.imageUrlToPageNum(img.src)]);
|
||||
// console.log(this.pageNum, ' Prefetched pages: ', pages.map(p => {
|
||||
// if (this.pageNum === p[1]) return '[' + p + ']';
|
||||
// return '' + p
|
||||
// }));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1681,4 +1692,5 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
d.text = translate('preferences.' + o.text);
|
||||
return d;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<ng-container *ngIf="isValid() && !this.mangaReaderService.shouldSplit(this.currentImage, this.pageSplit)">
|
||||
<div class="image-container {{imageFitClass$ | async}} {{emulateBookClass$ | async}}"
|
||||
[style.filter]="(darkness$ | async) ?? '' | safeStyle" [style.height]="(imageContainerHeight$ | async) ?? '' | safeStyle">
|
||||
<ng-container *ngIf="currentImage">
|
||||
<img alt=" "
|
||||
#image [src]="currentImage.src"
|
||||
id="image-1"
|
||||
class="{{imageFitClass$ | async}} {{readerModeClass$ | async}} {{showClickOverlayClass$ | async}}"
|
||||
>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
@if(isValid() && !mangaReaderService.shouldSplit(currentImage, pageSplit)) {
|
||||
<div class="image-container {{imageFitClass$ | async}} {{emulateBookClass$ | async}}"
|
||||
[style.filter]="(darkness$ | async) ?? '' | safeStyle" [style.height]="(imageContainerHeight$ | async) ?? '' | safeStyle">
|
||||
@if(currentImage) {
|
||||
<img alt=" "
|
||||
#image
|
||||
[src]="currentImage.src"
|
||||
id="image-1"
|
||||
class="{{imageFitClass$ | async}} {{readerModeClass$ | async}} {{showClickOverlayClass$ | async}}" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import { SafeStylePipe } from '../../../_pipes/safe-style.pipe';
|
|||
styleUrls: ['./single-renderer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, AsyncPipe, SafeStylePipe]
|
||||
imports: [AsyncPipe, SafeStylePipe]
|
||||
})
|
||||
export class SingleRendererComponent implements OnInit, ImageRenderer {
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ export class SingleRendererComponent implements OnInit, ImageRenderer {
|
|||
|
||||
return fit;
|
||||
}),
|
||||
shareReplay(),
|
||||
shareReplay({refCount: true, bufferSize: 1}),
|
||||
filter(_ => this.isValid()),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -153,7 +153,8 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
// This needs to only apply after first render
|
||||
this.libraryForm.get('type')?.valueChanges.pipe(
|
||||
tap((type: LibraryType) => {
|
||||
switch (type) {
|
||||
const libType = parseInt(type + '', 10) as LibraryType;
|
||||
switch (libType) {
|
||||
case LibraryType.Manga:
|
||||
this.libraryForm.get(FileTypeGroup.Archive + '')?.setValue(true);
|
||||
this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(true);
|
||||
|
|
@ -177,6 +178,7 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(true);
|
||||
this.libraryForm.get(FileTypeGroup.Pdf + '')?.setValue(false);
|
||||
this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(false);
|
||||
break;
|
||||
}
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
|
|
@ -203,15 +205,17 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
for(let glob of this.library.excludePatterns) {
|
||||
this.libraryForm.addControl('excludeGlob-' , new FormControl(glob, []));
|
||||
}
|
||||
this.excludePatterns = this.library.excludePatterns;
|
||||
} else {
|
||||
for(let fileTypeGroup of allFileTypeGroup) {
|
||||
this.libraryForm.addControl(fileTypeGroup + '', new FormControl(true, []));
|
||||
}
|
||||
}
|
||||
this.excludePatterns = this.library.excludePatterns;
|
||||
|
||||
if (this.excludePatterns.length === 0) {
|
||||
this.excludePatterns = [''];
|
||||
}
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue