Fixed a bug with double clicking manga reader on mobile no longer triggering bookmarking

This commit is contained in:
Joseph Milazzo 2024-12-08 11:09:43 -06:00
parent 2a43deea24
commit 0a7bc4b3f6
16 changed files with 250 additions and 203 deletions

View file

@ -0,0 +1 @@
<app-image [imageUrl]="imageUrl" height="32px" width="32px" classes="clickable" ngbTooltip="{{rating | ageRating}}" (click)="openRating()"></app-image>

View file

@ -0,0 +1,109 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Input,
OnChanges,
OnInit,
SimpleChanges
} from '@angular/core';
import {AgeRating} from "../../_models/metadata/age-rating";
import {ImageComponent} from "../../shared/image/image.component";
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {AgeRatingPipe} from "../../_pipes/age-rating.pipe";
import {AsyncPipe} from "@angular/common";
import {FilterUtilitiesService} from "../../shared/_services/filter-utilities.service";
import {FilterComparison} from "../../_models/metadata/v2/filter-comparison";
import {FilterField} from "../../_models/metadata/v2/filter-field";
const basePath = './assets/images/ratings/';
@Component({
selector: 'app-age-rating-image',
standalone: true,
imports: [
ImageComponent,
NgbTooltip,
AgeRatingPipe,
AsyncPipe
],
templateUrl: './age-rating-image.component.html',
styleUrl: './age-rating-image.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AgeRatingImageComponent implements OnInit, OnChanges {
private readonly cdRef = inject(ChangeDetectorRef);
private readonly filterUtilityService = inject(FilterUtilitiesService);
protected readonly AgeRating = AgeRating;
@Input({required: true}) rating: AgeRating = AgeRating.Unknown;
imageUrl: string = 'unknown-rating.png';
ngOnInit() {
this.setImage();
}
ngOnChanges() {
this.setImage();
}
setImage() {
switch (this.rating) {
case AgeRating.Unknown:
this.imageUrl = basePath + 'unknown-rating.png';
break;
case AgeRating.RatingPending:
this.imageUrl = basePath + 'rating-pending-rating.png';
break;
case AgeRating.EarlyChildhood:
this.imageUrl = basePath + 'early-childhood-rating.png';
break;
case AgeRating.Everyone:
this.imageUrl = basePath + 'everyone-rating.png';
break;
case AgeRating.G:
this.imageUrl = basePath + 'g-rating.png';
break;
case AgeRating.Everyone10Plus:
this.imageUrl = basePath + 'everyone-10+-rating.png';
break;
case AgeRating.PG:
this.imageUrl = basePath + 'pg-rating.png';
break;
case AgeRating.KidsToAdults:
this.imageUrl = basePath + 'kids-to-adults-rating.png';
break;
case AgeRating.Teen:
this.imageUrl = basePath + 'teen-rating.png';
break;
case AgeRating.Mature15Plus:
this.imageUrl = basePath + 'ma15+-rating.png';
break;
case AgeRating.Mature17Plus:
this.imageUrl = basePath + 'mature-17+-rating.png';
break;
case AgeRating.Mature:
this.imageUrl = basePath + 'm-rating.png';
break;
case AgeRating.R18Plus:
this.imageUrl = basePath + 'r18+-rating.png';
break;
case AgeRating.AdultsOnly:
this.imageUrl = basePath + 'adults-only-18+-rating.png';
break;
case AgeRating.X18Plus:
this.imageUrl = basePath + 'x18+-rating.png';
break;
}
this.cdRef.markForCheck();
}
openRating() {
this.filterUtilityService.applyFilter(['all-series'], FilterField.AgeRating, FilterComparison.Equal, `${this.rating}`).subscribe();
}
}

View file

@ -0,0 +1,39 @@
@if (publishers.length > 0) {
<div class="publisher-wrapper">
<div class="publisher-flipper" [class.is-flipped]="isFlipped">
<div class="publisher-side publisher-front">
<div class="publisher-img-container d-inline-flex align-items-center me-2 position-relative">
<app-image
[imageUrl]="imageService.getPublisherImage(currentPublisher!.name)"
[classes]="'me-2'"
[hideOnError]="true"
width="32px"
height="32px"
aria-hidden="true">
</app-image>
<div class="position-relative d-inline-block"
(click)="openPublisher(currentPublisher!.id)">
{{currentPublisher!.name}}
</div>
</div>
</div>
<div class="publisher-side publisher-back">
<div class="publisher-img-container d-inline-flex align-items-center me-2 position-relative">
<app-image
[imageUrl]="imageService.getPublisherImage(nextPublisher!.name)"
[classes]="'me-2'"
[hideOnError]="true"
width="32px"
height="32px"
aria-hidden="true">
</app-image>
<div class="position-relative d-inline-block"
(click)="openPublisher(nextPublisher!.id)">
{{nextPublisher!.name}}
</div>
</div>
</div>
</div>
</div>
}

View file

@ -0,0 +1,59 @@
//
//.publisher-img-container {
// background-color: var(--card-bg-color);
// border-radius: 3px;
// padding: 2px 5px;
// font-size: 0.8rem;
// vertical-align: middle;
//
// div {
// min-height: 32px;
// line-height: 32px;
// }
//}
// Animation code
.publisher-wrapper {
perspective: 1000px;
height: 32px;
background-color: var(--card-bg-color);
border-radius: 3px;
padding: 2px 5px;
font-size: 0.8rem;
vertical-align: middle;
div {
min-height: 32px;
line-height: 32px;
}
}
.publisher-flipper {
position: relative;
width: 100%;
height: 100%;
text-align: left;
transition: transform 0.6s ease;
transform-style: preserve-3d;
}
.publisher-flipper.is-flipped {
transform: rotateX(180deg);
}
.publisher-side {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
}
.publisher-front {
z-index: 2;
}
.publisher-back {
transform: rotateX(180deg);
}

View file

@ -0,0 +1,90 @@
import {
AfterViewChecked,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import {ImageComponent} from "../../shared/image/image.component";
import {FilterField} from "../../_models/metadata/v2/filter-field";
import {Person} from "../../_models/metadata/person";
import {ImageService} from "../../_services/image.service";
import {FilterComparison} from "../../_models/metadata/v2/filter-comparison";
import {FilterUtilitiesService} from "../../shared/_services/filter-utilities.service";
import {Router} from "@angular/router";
const ANIMATION_TIME = 3000;
@Component({
selector: 'app-publisher-flipper',
standalone: true,
imports: [
ImageComponent
],
templateUrl: './publisher-flipper.component.html',
styleUrl: './publisher-flipper.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PublisherFlipperComponent implements OnInit, OnDestroy, AfterViewInit, AfterViewChecked {
protected readonly imageService = inject(ImageService);
private readonly filterUtilityService = inject(FilterUtilitiesService);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly router = inject(Router);
@Input() publishers: Array<Person> = [];
currentPublisher: Person | undefined = undefined;
nextPublisher: Person | undefined = undefined;
currentIndex = 0;
isFlipped = false;
private intervalId: any;
ngOnInit() {
if (this.publishers.length > 0) {
this.currentPublisher = this.publishers[0];
this.nextPublisher = this.publishers[1] || this.publishers[0];
}
}
ngAfterViewInit() {
if (this.publishers.length > 1) {
this.startFlipping(); // Start flipping cycle once the view is initialized
}
}
ngAfterViewChecked() {
// This lifecycle hook will be called after Angular performs change detection in each cycle
if (this.isFlipped) {
// Only update publishers after the flip is complete
this.currentIndex = (this.currentIndex + 1) % this.publishers.length;
this.currentPublisher = this.publishers[this.currentIndex];
this.nextPublisher = this.publishers[(this.currentIndex + 1) % this.publishers.length];
}
}
ngOnDestroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
private startFlipping() {
this.intervalId = setInterval(() => {
// Toggle flip state, initiating the flip animation
this.isFlipped = !this.isFlipped;
this.cdRef.detectChanges(); // Explicitly detect changes to trigger re-render
}, ANIMATION_TIME);
}
openPublisher(filter: string | number) {
// TODO: once we build out publisher person-detail page, we can redirect there
this.filterUtilityService.applyFilter(['all-series'], FilterField.Publisher, FilterComparison.Equal, `${filter}`).subscribe();
}
}

View file

@ -0,0 +1,34 @@
<ng-container *transloco="let t; read: 'related-tab'">
<div style="padding-bottom: 1rem;">
@if (relations.length > 0) {
<app-carousel-reel [items]="relations" [title]="t('relations-title')">
<ng-template #carouselItem let-item>
<app-series-card class="col-auto mt-2 mb-2" [series]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
</ng-template>
</app-carousel-reel>
}
@if (collections.length > 0) {
<app-carousel-reel [items]="collections" [title]="t('collections-title')">
<ng-template #carouselItem let-item>
<app-card-item [title]="item.title" [entity]="item"
[count]="item.itemCount"
[suppressLibraryLink]="true" [imageUrl]="imageService.getCollectionCoverImage(item.id)"
(clicked)="openCollection(item)" [linkUrl]="'/collections/' + item.id" [showFormat]="false"></app-card-item>
</ng-template>
</app-carousel-reel>
}
@if (readingLists.length > 0) {
<app-carousel-reel [items]="readingLists" [title]="t('reading-lists-title')">
<ng-template #carouselItem let-item>
<app-card-item [title]="item.title" [entity]="item"
[count]="item.itemCount"
[suppressLibraryLink]="true" [imageUrl]="imageService.getReadingListCoverImage(item.id)"
(clicked)="openReadingList(item)" [linkUrl]="'/lists/' + item.id" [showFormat]="false"></app-card-item>
</ng-template>
</app-carousel-reel>
}
</div>
</ng-container>

View file

@ -0,0 +1,48 @@
import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core';
import {ReadingList} from "../../_models/reading-list";
import {CardItemComponent} from "../../cards/card-item/card-item.component";
import {CarouselReelComponent} from "../../carousel/_components/carousel-reel/carousel-reel.component";
import {ImageService} from "../../_services/image.service";
import {TranslocoDirective} from "@jsverse/transloco";
import {UserCollection} from "../../_models/collection-tag";
import {Router} from "@angular/router";
import {SeriesCardComponent} from "../../cards/series-card/series-card.component";
import {Series} from "../../_models/series";
import {RelationKind} from "../../_models/series-detail/relation-kind";
export interface RelatedSeriesPair {
series: Series;
relation: RelationKind;
}
@Component({
selector: 'app-related-tab',
standalone: true,
imports: [
CardItemComponent,
CarouselReelComponent,
TranslocoDirective,
SeriesCardComponent
],
templateUrl: './related-tab.component.html',
styleUrl: './related-tab.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RelatedTabComponent {
protected readonly imageService = inject(ImageService);
protected readonly router = inject(Router);
@Input() readingLists: Array<ReadingList> = [];
@Input() collections: Array<UserCollection> = [];
@Input() relations: Array<RelatedSeriesPair> = [];
openReadingList(readingList: ReadingList) {
this.router.navigate(['lists', readingList.id]);
}
openCollection(collection: UserCollection) {
this.router.navigate(['collections', collection.id]);
}
}