Round 3 of Bugfixing (#3318)
This commit is contained in:
parent
727fbd353b
commit
abdf15b895
40 changed files with 482 additions and 489 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import {IHasCover} from "../common/i-has-cover";
|
||||
|
||||
export enum PersonRole {
|
||||
Other = 1,
|
||||
Artist = 2,
|
||||
|
|
@ -16,7 +18,7 @@ export enum PersonRole {
|
|||
Location = 15
|
||||
}
|
||||
|
||||
export interface Person {
|
||||
export interface Person extends IHasCover {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
|
|
@ -26,6 +28,6 @@ export interface Person {
|
|||
aniListId?: number;
|
||||
hardcoverId?: string;
|
||||
asin?: string;
|
||||
primaryColor?: string;
|
||||
secondaryColor?: string;
|
||||
primaryColor: string;
|
||||
secondaryColor: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,10 @@ import {shareReplay} from "rxjs/operators";
|
|||
})
|
||||
export class LanguageNamePipe implements PipeTransform {
|
||||
|
||||
constructor(private metadataService: MetadataService) {
|
||||
}
|
||||
constructor(private metadataService: MetadataService) {}
|
||||
|
||||
transform(isoCode: string): Observable<string> {
|
||||
// TODO: See if we can speed this up. It rarely changes and is quite heavy to download on each page
|
||||
return this.metadataService.getAllValidLanguages().pipe(map(lang => {
|
||||
const l = lang.filter(l => l.isoCode === isoCode);
|
||||
if (l.length > 0) return l[0].title;
|
||||
return '';
|
||||
}), shareReplay());
|
||||
return this.metadataService.getLanguageNameForCode(isoCode).pipe(shareReplay());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {FilterStatement} from "../_models/metadata/v2/filter-statement";
|
|||
import {SeriesDetailPlus} from "../_models/series-detail/series-detail-plus";
|
||||
import {LibraryType} from "../_models/library/library";
|
||||
import {IHasCast} from "../_models/common/i-has-cast";
|
||||
import {TextResonse} from "../_types/text-response";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -77,6 +78,10 @@ export class MetadataService {
|
|||
return this.httpClient.get<Array<Language>>(this.baseUrl + method);
|
||||
}
|
||||
|
||||
getLanguageNameForCode(code: string) {
|
||||
return this.httpClient.get<string>(`${this.baseUrl}metadata/language-title?code=${code}`, TextResonse);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All the potential language tags there can be
|
||||
|
|
|
|||
|
|
@ -1,5 +1,43 @@
|
|||
<ng-container *transloco="let t; read: 'details-tab'">
|
||||
<div class="details pb-3">
|
||||
|
||||
@if (readingTime) {
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('read-time-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
{{readingTime | readTime}}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (releaseYear) {
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('release-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
{{releaseYear}}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (language) {
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('language-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
{{language | languageName | async}}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('format-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
<app-series-format [format]="format"></app-series-format> {{format | mangaFormat }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-section-break" aria-hidden="true"></div>
|
||||
|
||||
|
||||
<div class="mb-3 ms-1">
|
||||
<h4 class="header">{{t('genres-title')}}</h4>
|
||||
<div class="ms-3">
|
||||
|
|
|
|||
|
|
@ -3,18 +3,25 @@ import {CarouselReelComponent} from "../../carousel/_components/carousel-reel/ca
|
|||
import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.component";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {IHasCast} from "../../_models/common/i-has-cast";
|
||||
import {Person, PersonRole} from "../../_models/metadata/person";
|
||||
import {Router} from "@angular/router";
|
||||
import {PersonRole} from "../../_models/metadata/person";
|
||||
import {FilterField} from "../../_models/metadata/v2/filter-field";
|
||||
import {FilterComparison} from "../../_models/metadata/v2/filter-comparison";
|
||||
import {FilterUtilitiesService} from "../../shared/_services/filter-utilities.service";
|
||||
import {Genre} from "../../_models/metadata/genre";
|
||||
import {Tag} from "../../_models/tag";
|
||||
import {TagBadgeComponent, TagBadgeCursor} from "../../shared/tag-badge/tag-badge.component";
|
||||
import {TagBadgeComponent} from "../../shared/tag-badge/tag-badge.component";
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
|
||||
import {ImageService} from "../../_services/image.service";
|
||||
import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component";
|
||||
import {IHasReadingTime} from "../../_models/common/i-has-reading-time";
|
||||
import {ReadTimePipe} from "../../_pipes/read-time.pipe";
|
||||
import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
|
||||
import {MangaFormat} from "../../_models/manga-format";
|
||||
import {SeriesFormatComponent} from "../../shared/series-format/series-format.component";
|
||||
import {MangaFormatPipe} from "../../_pipes/manga-format.pipe";
|
||||
import {LanguageNamePipe} from "../../_pipes/language-name.pipe";
|
||||
import {AsyncPipe} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'app-details-tab',
|
||||
|
|
@ -26,7 +33,13 @@ import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander
|
|||
TagBadgeComponent,
|
||||
ImageComponent,
|
||||
SafeHtmlPipe,
|
||||
BadgeExpanderComponent
|
||||
BadgeExpanderComponent,
|
||||
ReadTimePipe,
|
||||
SentenceCasePipe,
|
||||
SeriesFormatComponent,
|
||||
MangaFormatPipe,
|
||||
LanguageNamePipe,
|
||||
AsyncPipe
|
||||
],
|
||||
templateUrl: './details-tab.component.html',
|
||||
styleUrl: './details-tab.component.scss',
|
||||
|
|
@ -41,6 +54,10 @@ export class DetailsTabComponent {
|
|||
protected readonly FilterField = FilterField;
|
||||
|
||||
@Input({required: true}) metadata!: IHasCast;
|
||||
@Input() readingTime: IHasReadingTime | undefined;
|
||||
@Input() language: string | undefined;
|
||||
@Input() format: MangaFormat = MangaFormat.UNKNOWN;
|
||||
@Input() releaseYear: number | undefined;
|
||||
@Input() genres: Array<Genre> = [];
|
||||
@Input() tags: Array<Tag> = [];
|
||||
@Input() webLinks: Array<string> = [];
|
||||
|
|
@ -50,4 +67,6 @@ export class DetailsTabComponent {
|
|||
if (queryParamName === FilterField.None) return;
|
||||
this.filterUtilityService.applyFilter(['all-series'], queryParamName, FilterComparison.Equal, `${filter}`).subscribe();
|
||||
}
|
||||
|
||||
protected readonly MangaFormat = MangaFormat;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ export class EditChapterModalComponent implements OnInit {
|
|||
};
|
||||
|
||||
personSettings.addTransformFn = ((title: string) => {
|
||||
return {id: 0, name: title, role: role, description: '', coverImage: '', coverImageLocked: false };
|
||||
return {id: 0, name: title, role: role, description: '', coverImage: '', coverImageLocked: false, primaryColor: '', secondaryColor: '' };
|
||||
});
|
||||
|
||||
return personSettings;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
<div class="form-group mb-3">
|
||||
<label for="discordId">{{t('activate-discordId-label')}}</label>
|
||||
<i class="fa fa-circle-info ms-1" aria-hidden="true" [ngbTooltip]="t('activate-discordId-tooltip')"></i>
|
||||
<a class="ms-1" [href]="WikiLink.KavitaPlusDiscordId" target="_blank" rel="noopener noreferrer">Help</a>
|
||||
<a class="ms-1" [href]="WikiLink.KavitaPlusDiscordId" target="_blank" rel="noopener noreferrer">{{t('help-label')}}</a>
|
||||
<input id="discordId" type="text" class="form-control" formControlName="discordId" autocomplete="off" [class.is-invalid]="formGroup.get('discordId')?.invalid && formGroup.get('discordId')?.touched"/>
|
||||
@if (formGroup.dirty || formGroup.touched) {
|
||||
<div id="inviteForm-validations" class="invalid-feedback">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@
|
|||
|
||||
</app-side-nav-companion-bar>
|
||||
|
||||
<app-manage-smart-filters></app-manage-smart-filters>
|
||||
<app-manage-smart-filters [target]="'_self'"></app-manage-smart-filters>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@
|
|||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'publicationStatusLocked' }"></ng-container>
|
||||
<select class="form-select" id="publication-status" formControlName="publicationStatus">
|
||||
@for (opt of publicationStatuses; track opt.value) {
|
||||
<option [value]="opt.value">{{opt.title | titlecase}}</option>
|
||||
<option [value]="opt.value">{{opt.value | publicationStatus}}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
};
|
||||
|
||||
personSettings.addTransformFn = ((title: string) => {
|
||||
return {id: 0, name: title, description: '', coverImageLocked: false };
|
||||
return {id: 0, name: title, description: '', coverImageLocked: false, primaryColor: '', secondaryColor: '' };
|
||||
});
|
||||
|
||||
return personSettings;
|
||||
|
|
@ -551,6 +551,7 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
// We only need to call updateSeries if we changed name, sort name, or localized name or reset a cover image
|
||||
const nameFieldsDirty = this.editSeriesForm.get('name')?.dirty || this.editSeriesForm.get('sortName')?.dirty || this.editSeriesForm.get('localizedName')?.dirty;
|
||||
const nameFieldLockChanged = this.series.nameLocked !== this.initSeries.nameLocked || this.series.sortNameLocked !== this.initSeries.sortNameLocked || this.series.localizedNameLocked !== this.initSeries.localizedNameLocked;
|
||||
|
||||
if (nameFieldsDirty || nameFieldLockChanged || this.coverImageReset) {
|
||||
model.nameLocked = this.series.nameLocked;
|
||||
model.sortNameLocked = this.series.sortNameLocked;
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@
|
|||
<div class="card-overlay"></div>
|
||||
<div class="chapter overlay-information">
|
||||
<div class="overlay-information--centered">
|
||||
<span class="card-title library mx-auto" style="width: auto;" (click)="read($event)">
|
||||
<!-- Card Image -->
|
||||
<div>
|
||||
<i class="fa-solid fa-book" aria-hidden="true"></i>
|
||||
</div>
|
||||
</span>
|
||||
<span class="card-title library mx-auto" style="width: auto;" (click)="read($event)">
|
||||
<!-- Card Image -->
|
||||
<div>
|
||||
<i class="fa-solid fa-book" aria-hidden="true"></i>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -85,10 +85,10 @@
|
|||
</a>
|
||||
</span>
|
||||
<span class="card-actions">
|
||||
@if (actions && actions.length > 0) {
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="chapter.titleName"></app-card-actionables>
|
||||
}
|
||||
</span>
|
||||
@if (actions && actions.length > 0) {
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="chapter.titleName"></app-card-actionables>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
<ng-container *transloco="let t; read: 'cover-image-chooser'">
|
||||
<div class="container-fluid" style="padding-left: 0px; padding-right: 0px">
|
||||
<div class="container-fluid" style="padding-left: 0; padding-right: 0">
|
||||
<form [formGroup]="form">
|
||||
<ngx-file-drop (onFileDrop)="dropped($event)"
|
||||
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" [accept]="acceptableExtensions" [directory]="false"
|
||||
dropZoneClassName="file-upload" contentClassName="file-upload-zone">
|
||||
<ng-template ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
|
||||
<div class="row g-0 mt-3 pb-3" *ngIf="mode === 'all'">
|
||||
<div class="mx-auto">
|
||||
<div class="row g-0 mb-3">
|
||||
<i class="fa fa-file-upload mx-auto" style="font-size: 24px; width: 20px;" aria-hidden="true"></i>
|
||||
</div>
|
||||
@if (mode === 'all') {
|
||||
<div class="row g-0 mt-3 pb-3">
|
||||
<div class="mx-auto">
|
||||
<div class="row g-0 mb-3">
|
||||
<i class="fa fa-file-upload mx-auto" style="font-size: 24px; width: 20px;" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="d-flex justify-content-evenly">
|
||||
<a class="pe-0" href="javascript:void(0)" (click)="changeMode('url')">
|
||||
<span class="phone-hidden">{{t('enter-an-url-pre-title', {url: ''})}}</span>{{t('url')}}
|
||||
</a>
|
||||
<span class="ps-1 pe-1">•</span>
|
||||
<span class="pe-0" href="javascript:void(0)">{{t('drag-n-drop')}}</span>
|
||||
<span class="ps-1 pe-1">•</span>
|
||||
<a class="pe-0" href="javascript:void(0)" (click)="openFileSelector()">{{t('upload')}}<span class="phone-hidden"> {{t('upload-continued')}}</span></a>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="d-flex justify-content-evenly">
|
||||
<a class="pe-0" href="javascript:void(0)" (click)="changeMode('url')">
|
||||
<span class="phone-hidden">{{t('enter-an-url-pre-title', {url: ''})}}</span>{{t('url')}}
|
||||
</a>
|
||||
<span class="ps-1 pe-1">•</span>
|
||||
<span class="pe-0" href="javascript:void(0)">{{t('drag-n-drop')}}</span>
|
||||
<span class="ps-1 pe-1">•</span>
|
||||
<a class="pe-0" href="javascript:void(0)" (click)="openFileSelector()">{{t('upload')}}<span class="phone-hidden"> {{t('upload-continued')}}</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-container *ngIf="mode === 'url'">
|
||||
} @else if (mode === 'url') {
|
||||
<div class="row g-0 mt-3 pb-3 ms-md-2 me-md-2">
|
||||
<div class="input-group col-auto me-md-2" style="width: 83%">
|
||||
<label class="input-group-text" for="load-image">{{t('url-label')}}</label>
|
||||
|
|
@ -42,9 +41,7 @@
|
|||
<span class="phone-hidden">{{t('back')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
}
|
||||
</ng-template>
|
||||
</ngx-file-drop>
|
||||
|
||||
|
|
@ -54,28 +51,32 @@
|
|||
</form>
|
||||
|
||||
<div class="row g-0 chooser" style="padding-top: 10px">
|
||||
<div class="clickable col-auto"
|
||||
*ngIf="showReset" tabindex="0" (click)="reset()"
|
||||
[ngClass]="{'selected': !showApplyButton && selectedIndex === -1}">
|
||||
<app-image class="card-img-top" [title]="t('reset-cover-tooltip')" height="232.91px" width="160px" [imageUrl]="imageService.resetCoverImage"></app-image>
|
||||
<ng-container *ngIf="showApplyButton">
|
||||
<br>
|
||||
<button style="width: 100%;" class="btn btn-secondary" (click)="resetImage()">{{t('reset')}}</button>
|
||||
</ng-container>
|
||||
@if (showReset) {
|
||||
<div class="clickable col-auto" tabindex="0" (click)="reset()"
|
||||
[ngClass]="{'selected': !showApplyButton && selectedIndex === -1}">
|
||||
<app-image class="card-img-top" [title]="t('reset-cover-tooltip')" height="232.91px" width="160px" [imageUrl]="imageService.resetCoverImage"></app-image>
|
||||
@if (showApplyButton) {
|
||||
<br>
|
||||
<button style="width: 100%;" class="btn btn-secondary" (click)="resetImage()">{{t('reset')}}</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@for (url of imageUrls; track url; let idx = $index) {
|
||||
<div class="clickable col-auto" tabindex="0" [attr.aria-label]="t('image-num', {num: idx + 1})" (click)="selectImage(idx)"
|
||||
[ngClass]="{'selected': !showApplyButton && selectedIndex === idx}">
|
||||
<app-image class="card-img-top" height="232.91px" width="160px" [imageUrl]="url" [processEvents]="idx > 0"></app-image>
|
||||
@if (showApplyButton) {
|
||||
<br>
|
||||
<button class="btn btn-primary" style="width: 100%;"
|
||||
(click)="applyImage(idx)">
|
||||
{{appliedIndex === idx ? t('applied') : t('apply')}}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
<div class="clickable col-auto"
|
||||
*ngFor="let url of imageUrls; let idx = index;" tabindex="0" [attr.aria-label]="t('image-num', {num: idx + 1})" (click)="selectImage(idx)"
|
||||
[ngClass]="{'selected': !showApplyButton && selectedIndex === idx}">
|
||||
<app-image class="card-img-top" height="232.91px" width="160px" [imageUrl]="url" [processEvents]="idx > 0"></app-image>
|
||||
<ng-container *ngIf="showApplyButton">
|
||||
<br>
|
||||
<button class="btn btn-primary" style="width: 100%;"
|
||||
(click)="applyImage(idx)">
|
||||
{{appliedIndex === idx ? t('applied') : t('apply')}}
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { ToastrService } from 'ngx-toastr';
|
|||
import { ImageService } from 'src/app/_services/image.service';
|
||||
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
|
||||
import { UploadService } from 'src/app/_services/upload.service';
|
||||
import {CommonModule, DOCUMENT} from '@angular/common';
|
||||
import {DOCUMENT, NgClass} from '@angular/common';
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {translate, TranslocoModule} from "@jsverse/transloco";
|
||||
|
||||
|
|
@ -27,9 +27,9 @@ import {translate, TranslocoModule} from "@jsverse/transloco";
|
|||
imports: [
|
||||
ReactiveFormsModule,
|
||||
NgxFileDropModule,
|
||||
CommonModule,
|
||||
ImageComponent,
|
||||
TranslocoModule
|
||||
TranslocoModule,
|
||||
NgClass
|
||||
],
|
||||
templateUrl: './cover-image-chooser.component.html',
|
||||
styleUrls: ['./cover-image-chooser.component.scss'],
|
||||
|
|
|
|||
|
|
@ -1,87 +1,94 @@
|
|||
<ng-container *transloco="let t; read: 'entity-title'">
|
||||
@switch (libraryType) {
|
||||
@case (LibraryType.Comic) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
@if (isChapter && includeChapter) {
|
||||
{{t('issue-num') + ' ' + number + ' - ' }}
|
||||
}
|
||||
{{renderText | defaultValue}}
|
||||
<!-- @switch (libraryType) {-->
|
||||
<!-- @case (LibraryType.Comic) {-->
|
||||
<!-- @if (titleName !== '' && prioritizeTitleName) {-->
|
||||
<!-- @if (isChapter && includeChapter) {-->
|
||||
<!-- {{t('issue-num') + ' ' + number + ' - ' }}-->
|
||||
<!-- }-->
|
||||
|
||||
{{titleName}}
|
||||
} @else {
|
||||
@if (includeVolume && volumeTitle !== '') {
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
}
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter ? t('issue-num') + number : volumeTitle) : t('special')}}
|
||||
}
|
||||
}
|
||||
<!-- {{titleName}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- @if (includeVolume && volumeTitle !== '') {-->
|
||||
<!-- {{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}-->
|
||||
<!-- }-->
|
||||
<!-- {{number !== LooseLeafOrSpecial ? (isChapter ? t('issue-num') + number : volumeTitle) : t('special')}}-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
@case (LibraryType.ComicVine) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
@if (isChapter && includeChapter) {
|
||||
{{t('issue-num') + ' ' + number + ' - ' }}
|
||||
}
|
||||
<!-- @case (LibraryType.ComicVine) {-->
|
||||
<!-- @if (titleName !== '' && prioritizeTitleName) {-->
|
||||
<!-- @if (isChapter && includeChapter) {-->
|
||||
<!-- {{t('issue-num') + ' ' + number + ' - ' }}-->
|
||||
<!-- }-->
|
||||
|
||||
{{titleName}}
|
||||
} @else {
|
||||
@if (includeVolume && volumeTitle !== '') {
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
}
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter ? t('issue-num') + number : volumeTitle) : t('special')}}
|
||||
}
|
||||
}
|
||||
<!-- {{titleName}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- @if (includeVolume && volumeTitle !== '') {-->
|
||||
<!-- {{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}-->
|
||||
<!-- }-->
|
||||
<!-- @if (number !== LooseLeafOrSpecial) {-->
|
||||
<!-- {{isChapter ? t('issue-num') + number : volumeTitle}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- {{t('special')}}-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
@case (LibraryType.Manga) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
@if (isChapter && includeChapter) {
|
||||
@if (number === LooseLeafOrSpecial) {
|
||||
{{t('chapter') + ' - ' }}
|
||||
} @else {
|
||||
{{t('chapter') + ' ' + number + ' - ' }}
|
||||
}
|
||||
<!-- @case (LibraryType.Manga) {-->
|
||||
<!-- @if (titleName !== '' && prioritizeTitleName) {-->
|
||||
<!-- @if (isChapter && includeChapter) {-->
|
||||
<!-- @if (number === LooseLeafOrSpecial) {-->
|
||||
<!-- {{t('chapter') + ' - ' }}-->
|
||||
<!-- } @else {-->
|
||||
<!-- {{t('chapter') + ' ' + number + ' - ' }}-->
|
||||
<!-- }-->
|
||||
|
||||
}
|
||||
{{titleName}}
|
||||
} @else {
|
||||
@if (includeVolume && volumeTitle !== '') {
|
||||
@if (number !== LooseLeafOrSpecial && isChapter && includeVolume) {
|
||||
{{volumeTitle}}
|
||||
}
|
||||
}
|
||||
<!-- }-->
|
||||
<!-- {{titleName}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- @if (includeVolume && volumeTitle !== '') {-->
|
||||
<!-- @if (number !== LooseLeafOrSpecial && isChapter && includeVolume) {-->
|
||||
<!-- {{volumeTitle}}-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
@if (number !== LooseLeafOrSpecial) {
|
||||
@if (isChapter) {
|
||||
{{t('chapter') + ' ' + number}}
|
||||
} @else {
|
||||
{{volumeTitle}}
|
||||
}
|
||||
} @else {
|
||||
{{t('special')}}
|
||||
}
|
||||
}
|
||||
}
|
||||
<!-- @if (number !== LooseLeafOrSpecial) {-->
|
||||
<!-- @if (isChapter) {-->
|
||||
<!-- {{t('chapter') + ' ' + number}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- {{volumeTitle}}-->
|
||||
<!-- }-->
|
||||
<!-- } @else if (fallbackToVolume && isChapter && volumeTitle) {-->
|
||||
<!-- {{t('vol-num', {num: volumeTitle})}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- {{t('special')}}-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
@case (LibraryType.Book) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
{{titleName}}
|
||||
} @else if (number === LooseLeafOrSpecial) {
|
||||
{{null | defaultValue}}
|
||||
} @else {
|
||||
{{t('book-num', {num: volumeTitle})}}
|
||||
}
|
||||
}
|
||||
<!-- @case (LibraryType.Book) {-->
|
||||
<!-- @if (titleName !== '' && prioritizeTitleName) {-->
|
||||
<!-- {{titleName}}-->
|
||||
<!-- } @else if (number === LooseLeafOrSpecial) {-->
|
||||
<!-- {{null | defaultValue}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- {{t('book-num', {num: volumeTitle})}}-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
@case (LibraryType.LightNovel) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
{{titleName}}
|
||||
} @else if (number === LooseLeafOrSpecial) {
|
||||
{{null | defaultValue}}
|
||||
} @else {
|
||||
{{t('book-num', {num: (isChapter ? number : volumeTitle)})}}
|
||||
}
|
||||
}
|
||||
<!-- @case (LibraryType.LightNovel) {-->
|
||||
<!-- @if (titleName !== '' && prioritizeTitleName) {-->
|
||||
<!-- {{titleName}}-->
|
||||
<!-- } @else if (number === LooseLeafOrSpecial) {-->
|
||||
<!-- {{null | defaultValue}}-->
|
||||
<!-- } @else {-->
|
||||
<!-- {{t('book-num', {num: (isChapter ? number : volumeTitle)})}}-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
@case (LibraryType.Images) {
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + number : volumeTitle) : t('special')}}
|
||||
}
|
||||
}
|
||||
<!-- @case (LibraryType.Images) {-->
|
||||
<!-- {{number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + number : volumeTitle) : t('special')}}-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnInit} from '@angular/core';
|
||||
import { UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Chapter, LooseLeafOrDefaultNumber } from 'src/app/_models/chapter';
|
||||
import { LibraryType } from 'src/app/_models/library/library';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
import {TranslocoModule} from "@jsverse/transloco";
|
||||
import {translate, TranslocoModule} from "@jsverse/transloco";
|
||||
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
||||
|
||||
/**
|
||||
|
|
@ -22,6 +22,9 @@ import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
|||
})
|
||||
export class EntityTitleComponent implements OnInit {
|
||||
|
||||
private readonly utilityService = inject(UtilityService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
|
||||
protected readonly LooseLeafOrSpecial = LooseLeafOrDefaultNumber + "";
|
||||
protected readonly LibraryType = LibraryType;
|
||||
|
||||
|
|
@ -42,16 +45,18 @@ export class EntityTitleComponent implements OnInit {
|
|||
* When a titleName (aka a title) is available on the entity, show it over Volume X Chapter Y
|
||||
*/
|
||||
@Input() prioritizeTitleName: boolean = true;
|
||||
/**
|
||||
* When there is no meaningful title to display and the chapter is just a single volume, show the volume number
|
||||
*/
|
||||
@Input() fallbackToVolume: boolean = true;
|
||||
|
||||
isChapter = false;
|
||||
titleName: string = '';
|
||||
volumeTitle: string = '';
|
||||
|
||||
number: string = '';
|
||||
renderText: string = '';
|
||||
|
||||
|
||||
constructor(private utilityService: UtilityService, private readonly cdRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isChapter = this.utilityService.isChapter(this.entity);
|
||||
|
||||
|
|
@ -60,7 +65,6 @@ export class EntityTitleComponent implements OnInit {
|
|||
this.volumeTitle = c.volumeTitle || '';
|
||||
this.titleName = c.titleName || '';
|
||||
this.number = c.range;
|
||||
|
||||
} else {
|
||||
const v = this.utilityService.asVolume(this.entity);
|
||||
this.volumeTitle = v.name || '';
|
||||
|
|
@ -70,6 +74,125 @@ export class EntityTitleComponent implements OnInit {
|
|||
}
|
||||
this.number = v.name;
|
||||
}
|
||||
|
||||
this.calculateRenderText();
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
private calculateRenderText() {
|
||||
switch (this.libraryType) {
|
||||
case LibraryType.Manga:
|
||||
this.renderText = this.calculateMangaRenderText();
|
||||
break;
|
||||
case LibraryType.Comic:
|
||||
this.renderText = this.calculateComicRenderText();
|
||||
break;
|
||||
case LibraryType.Book:
|
||||
this.renderText = this.calculateBookRenderText();
|
||||
break;
|
||||
case LibraryType.Images:
|
||||
this.renderText = this.calculateImageRenderText();
|
||||
break;
|
||||
case LibraryType.LightNovel:
|
||||
this.renderText = this.calculateLightNovelRenderText();
|
||||
break;
|
||||
case LibraryType.ComicVine:
|
||||
this.renderText = this.calculateComicRenderText();
|
||||
break;
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
private calculateBookRenderText() {
|
||||
let renderText = '';
|
||||
if (this.titleName !== '' && this.prioritizeTitleName) {
|
||||
renderText = this.titleName;
|
||||
} else if (this.number === this.LooseLeafOrSpecial) {
|
||||
renderText = '';
|
||||
} else {
|
||||
renderText = translate('entity-title.book-num', {num: this.volumeTitle});
|
||||
}
|
||||
return renderText;
|
||||
}
|
||||
|
||||
private calculateLightNovelRenderText() {
|
||||
let renderText = '';
|
||||
if (this.titleName !== '' && this.prioritizeTitleName) {
|
||||
renderText = this.titleName;
|
||||
} else if (this.number === this.LooseLeafOrSpecial) {
|
||||
renderText = '';
|
||||
} else {
|
||||
const bookNum = this.isChapter ? this.number : this.volumeTitle;
|
||||
renderText = translate('entity-title.book-num', {num: bookNum});
|
||||
}
|
||||
return renderText;
|
||||
}
|
||||
|
||||
private calculateMangaRenderText() {
|
||||
let renderText = '';
|
||||
|
||||
if (this.titleName !== '' && this.prioritizeTitleName) {
|
||||
if (this.isChapter && this.includeChapter) {
|
||||
if (this.number === this.LooseLeafOrSpecial) {
|
||||
renderText = translate('entity-title.chapter') + ' - ';
|
||||
} else {
|
||||
renderText = translate('entity-title.chapter') + ' ' + this.number + ' - ';
|
||||
}
|
||||
}
|
||||
|
||||
renderText += this.titleName;
|
||||
} else {
|
||||
if (this.includeVolume && this.volumeTitle !== '') {
|
||||
if (this.number !== this.LooseLeafOrSpecial && this.isChapter && this.includeVolume) {
|
||||
renderText = this.volumeTitle;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.number !== this.LooseLeafOrSpecial) {
|
||||
if (this.isChapter) {
|
||||
renderText = translate('entity-title.chapter') + ' ' + this.number;
|
||||
} else {
|
||||
renderText = this.volumeTitle;
|
||||
}
|
||||
} else if (this.fallbackToVolume && this.isChapter && this.volumeTitle) {
|
||||
renderText = translate('entity-title.vol-num', {num: this.volumeTitle});
|
||||
} else if (this.fallbackToVolume && this.isChapter) { // this.volumeTitle === '' (this is a single volume on volume detail page)
|
||||
renderText = translate('entity-title.single-volume');
|
||||
} else {
|
||||
renderText = translate('entity-title.special');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return renderText;
|
||||
}
|
||||
|
||||
private calculateImageRenderText() {
|
||||
let renderText = '';
|
||||
|
||||
if (this.number !== this.LooseLeafOrSpecial) {
|
||||
if (this.isChapter) {
|
||||
renderText = translate('entity-title.chapter') + ' ' + this.number;
|
||||
} else {
|
||||
renderText = this.volumeTitle;
|
||||
}
|
||||
} else {
|
||||
renderText = translate('entity-title.special');
|
||||
}
|
||||
|
||||
return renderText;
|
||||
}
|
||||
|
||||
|
||||
private calculateComicRenderText() {
|
||||
let renderText = '';
|
||||
if (this.titleName !== '' && this.prioritizeTitleName) {
|
||||
if (this.isChapter && this.includeChapter) {
|
||||
renderText = translate('entity-title.issue-num') + ' ' + this.number + ' - ';
|
||||
}
|
||||
renderText += this.titleName;
|
||||
}
|
||||
return renderText;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,13 @@
|
|||
<a ngbNavLink>{{t('details-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
||||
<app-details-tab [metadata]="chapter" [genres]="chapter.genres" [tags]="chapter.tags" [webLinks]="weblinks"></app-details-tab>
|
||||
<app-details-tab [metadata]="chapter"
|
||||
[genres]="chapter.genres"
|
||||
[tags]="chapter.tags"
|
||||
[webLinks]="weblinks"
|
||||
[readingTime]="chapter"
|
||||
[language]="chapter.language"
|
||||
[format]="series.format"></app-details-tab>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@
|
|||
<div class="row mt-2">
|
||||
<app-carousel-reel [items]="(chaptersByRole[role] | async)!" [title]="t('individual-role-title', {role: (role | personRole)})" (sectionClick)="loadFilterByRole(role)">
|
||||
<ng-template #carouselItem let-item>
|
||||
<app-chapter-card [chapter]="item" [libraryId]="item.libraryId" [libraryType]="item.libraryType" [seriesId]="item.seriesId"></app-chapter-card>
|
||||
<app-chapter-card [chapter]="item" [libraryId]="item.libraryId" [libraryType]="item.libraryType" [seriesId]="item.seriesId">
|
||||
|
||||
</app-chapter-card>
|
||||
</ng-template>
|
||||
</app-carousel-reel>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@
|
|||
<!-- Spacer -->
|
||||
<div class="col" aria-hidden="true"></div>
|
||||
<div class="col-auto ms-1">
|
||||
<a class="btn btn-icon" [href]="WikiLink.ReadingListCBL" target="_blank" rel="noopener noreferrer">Help</a>
|
||||
<a class="btn btn-icon" [href]="WikiLink.ReadingListCBL" target="_blank" rel="noopener noreferrer">{{t('help-label')}}</a>
|
||||
</div>
|
||||
<div class="col-auto ms-1">
|
||||
<button type="button" class="btn btn-primary" (click)="prevStep()" [disabled]="!canMoveToPrevStep()">{{t('prev')}}</button>
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@
|
|||
<virtual-scroller #scroll [items]="storylineItems" [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) {
|
||||
@for(item of scroll.viewPortItems; let idx = $index; track item) {
|
||||
@if (item.isChapter) {
|
||||
<ng-container [ngTemplateOutlet]="nonSpecialChapterCard" [ngTemplateOutletContext]="{$implicit: item.chapter, scroll: scroll, idx: idx, chaptersLength: storyChapters.length}"></ng-container>
|
||||
} @else {
|
||||
|
|
@ -214,7 +214,7 @@
|
|||
@defer (when activeTabId === TabID.Volumes; prefetch on idle) {
|
||||
<virtual-scroller #scroll [items]="volumes" [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) {
|
||||
@for (item of scroll.viewPortItems; let idx = $index; track item.id + '_' + item.pagesRead + + '_volumes') {
|
||||
<ng-container [ngTemplateOutlet]="nonChapterVolumeCard" [ngTemplateOutletContext]="{$implicit: item, scroll: scroll, idx: idx, totalLength: volumes.length}"></ng-container>
|
||||
}
|
||||
<ng-container [ngTemplateOutlet]="estimatedNextCard" [ngTemplateOutletContext]="{tabId: TabID.Volumes}"></ng-container>
|
||||
|
|
@ -235,7 +235,7 @@
|
|||
@defer (when activeTabId === TabID.Chapters; prefetch on idle) {
|
||||
<virtual-scroller #scroll [items]="chapters" [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) {
|
||||
@for (item of scroll.viewPortItems; let idx = $index; track item.id + '_' + item.pagesRead + '_chapters') {
|
||||
<ng-container [ngTemplateOutlet]="nonSpecialChapterCard" [ngTemplateOutletContext]="{$implicit: item, scroll: scroll, idx: idx, totalLength: chapters.length}"></ng-container>
|
||||
}
|
||||
<ng-container [ngTemplateOutlet]="estimatedNextCard" [ngTemplateOutletContext]="{tabId: TabID.Chapters}"></ng-container>
|
||||
|
|
@ -256,7 +256,7 @@
|
|||
@defer (when activeTabId === TabID.Specials; prefetch on idle) {
|
||||
<virtual-scroller #scroll [items]="specials" [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) {
|
||||
@for(item of scroll.viewPortItems; let idx = $index; track item.id + '_' + item.pagesRead + '_specials') {
|
||||
<ng-container [ngTemplateOutlet]="specialChapterCard" [ngTemplateOutletContext]="{$implicit: item, scroll: scroll, idx: idx, chaptersLength: chapters.length}"></ng-container>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -338,7 +338,15 @@
|
|||
<a ngbNavLink>{{t(TabID.Details)}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
||||
<app-details-tab [metadata]="seriesMetadata" [genres]="seriesMetadata.genres" [tags]="seriesMetadata.tags" [webLinks]="WebLinks"></app-details-tab>
|
||||
<app-details-tab [metadata]="seriesMetadata"
|
||||
[genres]="seriesMetadata.genres"
|
||||
[tags]="seriesMetadata.tags"
|
||||
[webLinks]="WebLinks"
|
||||
[readingTime]="series"
|
||||
[releaseYear]="seriesMetadata.releaseYear"
|
||||
[language]="seriesMetadata.language"
|
||||
[format]="series.format">
|
||||
</app-details-tab>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<i class="fa-solid fa-triangle-exclamation red me-2" [ngbTooltip]="t('errored')"></i>
|
||||
<span class="visually-hidden">{{t('errored')}}</span>
|
||||
}
|
||||
<a [href]="baseUrl + 'all-series?' + f.filter" target="_blank">{{f.name}}</a>
|
||||
<a [href]="baseUrl + 'all-series?' + f.filter" [target]="target">{{f.name}}</a>
|
||||
</span>
|
||||
<button class="btn btn-danger float-end" (click)="deleteFilter(f)">
|
||||
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input} from '@angular/core';
|
||||
import {FilterService} from "../../../_services/filter.service";
|
||||
import {SmartFilter} from "../../../_models/metadata/v2/smart-filter";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
|
|
@ -24,6 +24,8 @@ export class ManageSmartFiltersComponent {
|
|||
private readonly actionService = inject(ActionService);
|
||||
protected readonly baseUrl = inject(APP_BASE_HREF);
|
||||
|
||||
@Input() target: '_self' | '_blank' = '_blank';
|
||||
|
||||
filters: Array<SmartFilter> = [];
|
||||
listForm: FormGroup = new FormGroup({
|
||||
'filterQuery': new FormControl('', [])
|
||||
|
|
|
|||
|
|
@ -196,7 +196,12 @@
|
|||
<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>
|
||||
<app-details-tab [metadata]="volumeCast"
|
||||
[genres]="genres"
|
||||
[tags]="tags"
|
||||
[readingTime]="volume"
|
||||
[language]="volume.chapters[0].language"
|
||||
[format]="series.format"></app-details-tab>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue