Lots of Bugfixes (#3308)
This commit is contained in:
parent
ed7e9d4a6e
commit
fc269d3dd2
36 changed files with 331 additions and 588 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {TranslocoService} from "@jsverse/transloco";
|
||||
import {HourEstimateRange} from "../_models/series-detail/hour-estimate-range";
|
||||
import {DecimalPipe} from "@angular/common";
|
||||
|
||||
@Pipe({
|
||||
name: 'readTimeLeft',
|
||||
|
|
@ -8,9 +9,31 @@ import {HourEstimateRange} from "../_models/series-detail/hour-estimate-range";
|
|||
})
|
||||
export class ReadTimeLeftPipe implements PipeTransform {
|
||||
|
||||
constructor(private translocoService: TranslocoService) {}
|
||||
constructor(private readonly translocoService: TranslocoService) {}
|
||||
|
||||
transform(readingTimeLeft: HourEstimateRange): string {
|
||||
return `~${readingTimeLeft.avgHours} ${readingTimeLeft.avgHours > 1 ? this.translocoService.translate('read-time-pipe.hours') : this.translocoService.translate('read-time-pipe.hour')}`;
|
||||
const hoursLabel = readingTimeLeft.avgHours > 1
|
||||
? this.translocoService.translate('read-time-pipe.hours')
|
||||
: this.translocoService.translate('read-time-pipe.hour');
|
||||
|
||||
const formattedHours = this.customRound(readingTimeLeft.avgHours);
|
||||
|
||||
return `~${formattedHours} ${hoursLabel}`;
|
||||
}
|
||||
|
||||
private customRound(value: number): string {
|
||||
const integerPart = Math.floor(value);
|
||||
const decimalPart = value - integerPart;
|
||||
|
||||
if (decimalPart < 0.5) {
|
||||
// Round down to the nearest whole number
|
||||
return integerPart.toString();
|
||||
} else if (decimalPart >= 0.5 && decimalPart < 0.9) {
|
||||
// Return with 1 decimal place
|
||||
return value.toFixed(1);
|
||||
} else {
|
||||
// Round up to the nearest whole number
|
||||
return Math.ceil(value).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ export class ActionService {
|
|||
this.readingListModalRef.componentInstance.seriesId = seriesId;
|
||||
this.readingListModalRef.componentInstance.volumeIds = volumes.map(v => v.id);
|
||||
this.readingListModalRef.componentInstance.chapterIds = chapters?.map(c => c.id);
|
||||
this.readingListModalRef.componentInstance.title = translate('action.multiple-selections');
|
||||
this.readingListModalRef.componentInstance.title = translate('actionable.multiple-selections');
|
||||
this.readingListModalRef.componentInstance.type = ADD_FLOW.Multiple;
|
||||
|
||||
|
||||
|
|
@ -530,7 +530,7 @@ export class ActionService {
|
|||
if (this.readingListModalRef != null) { return; }
|
||||
this.readingListModalRef = this.modalService.open(AddToListModalComponent, { scrollable: true, size: 'md', fullscreen: 'md' });
|
||||
this.readingListModalRef.componentInstance.seriesIds = series.map(v => v.id);
|
||||
this.readingListModalRef.componentInstance.title = translate('action.multiple-selections');
|
||||
this.readingListModalRef.componentInstance.title = translate('actionable.multiple-selections');
|
||||
this.readingListModalRef.componentInstance.type = ADD_FLOW.Multiple_Series;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class PersonService {
|
|||
let params = new HttpParams();
|
||||
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
|
||||
|
||||
return this.httpClient.post<PaginatedResult<BrowsePerson[]>>(this.baseUrl + 'person/authors', {}, {observe: 'response', params}).pipe(
|
||||
return this.httpClient.post<PaginatedResult<BrowsePerson[]>>(this.baseUrl + 'person/all', {}, {observe: 'response', params}).pipe(
|
||||
map((response: any) => {
|
||||
return this.utilityService.createPaginatedResult(response) as PaginatedResult<BrowsePerson[]>;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,21 +2,23 @@
|
|||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{t('title')}}</h4>
|
||||
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="modal.close()"></button>
|
||||
<button type="button" class="btn-close" aria-label="close" (click)="modal.close()"></button>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal">
|
||||
@if (currentLevel.length > 0) {
|
||||
<button class="btn btn-secondary w-100 mb-3 text-start" (click)="handleBack()">
|
||||
← {{t('back-to', {action: t(currentLevel[currentLevel.length - 1])})}}
|
||||
← {{t('back-to', {action: currentLevel[currentLevel.length - 1]})}}
|
||||
</button>
|
||||
}
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
|
||||
|
||||
@for (action of currentItems; track action.title) {
|
||||
@if (willRenderAction(action)) {
|
||||
<button class="btn btn-outline-primary text-start d-flex justify-content-between align-items-center w-100"
|
||||
(click)="handleItemClick(action)">
|
||||
{{t(action.title)}}
|
||||
{{action.title}}
|
||||
@if (action.children.length > 0 || action.dynamicList) {
|
||||
<span class="ms-1">→</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
Output
|
||||
} from '@angular/core';
|
||||
import {NgClass} from "@angular/common";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {Breakpoint, UtilityService} from "../../shared/_services/utility.service";
|
||||
import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {Action, ActionItem} from "../../_services/action-factory.service";
|
||||
|
|
@ -48,7 +48,8 @@ export class ActionableModalComponent implements OnInit {
|
|||
user!: User | undefined;
|
||||
|
||||
ngOnInit() {
|
||||
this.currentItems = this.actions;
|
||||
this.currentItems = this.translateOptions(this.actions);
|
||||
|
||||
this.accountService.currentUser$.pipe(tap(user => {
|
||||
this.user = user;
|
||||
this.cdRef.markForCheck();
|
||||
|
|
@ -58,17 +59,21 @@ export class ActionableModalComponent implements OnInit {
|
|||
handleItemClick(item: ActionItem<any>) {
|
||||
if (item.children && item.children.length > 0) {
|
||||
this.currentLevel.push(item.title);
|
||||
this.currentItems = item.children;
|
||||
} else if (item.dynamicList) {
|
||||
item.dynamicList.subscribe(dynamicItems => {
|
||||
this.currentLevel.push(item.title);
|
||||
this.currentItems = dynamicItems.map(di => ({
|
||||
...item,
|
||||
title: di.title,
|
||||
_extra: di
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
|
||||
if (item.children.length === 1 && item.children[0].dynamicList) {
|
||||
item.children[0].dynamicList.subscribe(dynamicItems => {
|
||||
this.currentItems = dynamicItems.map(di => ({
|
||||
...item,
|
||||
children: [], // Required as dynamic list is only one deep
|
||||
title: di.title,
|
||||
_extra: di
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
this.currentItems = this.translateOptions(item.children);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.actionPerformed.emit(item);
|
||||
this.modal.close(item);
|
||||
}
|
||||
|
|
@ -84,15 +89,15 @@ export class ActionableModalComponent implements OnInit {
|
|||
items = items.find(item => item.title === level)?.children || [];
|
||||
}
|
||||
|
||||
this.currentItems = items;
|
||||
this.currentItems = this.translateOptions(items);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
// willRenderAction(action: ActionItem<any>) {
|
||||
// if (this.user === undefined) return false;
|
||||
//
|
||||
// return this.accountService.canInvokeAction(this.user, action.action);
|
||||
// }
|
||||
translateOptions(opts: Array<ActionItem<any>>) {
|
||||
return opts.map(a => {
|
||||
return {...a, title: translate('actionable.' + a.title)};
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ export class CardActionablesComponent implements OnInit {
|
|||
openMobileActionableMenu(event: any) {
|
||||
this.preventEvent(event);
|
||||
|
||||
const ref = this.modalService.open(ActionableModalComponent, {fullscreen: 'sm'});
|
||||
const ref = this.modalService.open(ActionableModalComponent, {fullscreen: true, centered: true});
|
||||
ref.componentInstance.actions = this.actions;
|
||||
ref.componentInstance.willRenderAction = this.willRenderAction.bind(this);
|
||||
ref.componentInstance.shouldRenderSubMenu = this.shouldRenderSubMenu.bind(this);
|
||||
|
|
|
|||
|
|
@ -1,31 +1,32 @@
|
|||
<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 (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"
|
||||
[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 (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"
|
||||
[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"
|
||||
[suppressLibraryLink]="true" [imageUrl]="imageService.getReadingListCoverImage(item.id)"
|
||||
(clicked)="openReadingList(item)" [linkUrl]="'/lists/' + 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"
|
||||
[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>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import {filter, map} from "rxjs/operators";
|
|||
import {UserProgressUpdateEvent} from "../../_models/events/user-progress-update-event";
|
||||
import {ReaderService} from "../../_services/reader.service";
|
||||
import {LibraryType} from "../../_models/library/library";
|
||||
import {Device} from "../../_models/device/device";
|
||||
import {ActionService} from "../../_services/action.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-chapter-card',
|
||||
|
|
@ -59,6 +61,7 @@ export class ChapterCardComponent implements OnInit {
|
|||
public readonly imageService = inject(ImageService);
|
||||
public readonly bulkSelectionService = inject(BulkSelectionService);
|
||||
private readonly downloadService = inject(DownloadService);
|
||||
private readonly actionService = inject(ActionService);
|
||||
private readonly messageHub = inject(MessageHubService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
|
|
@ -183,6 +186,12 @@ export class ChapterCardComponent implements OnInit {
|
|||
return; // Don't propagate the download from a card
|
||||
}
|
||||
|
||||
if (action.action == Action.SendTo) {
|
||||
const device = (action._extra!.data as Device);
|
||||
this.actionService.sendToDevice([this.chapter.id], device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action, this.chapter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@
|
|||
<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>
|
||||
<input type="text" autofocus autocomplete="off" class="form-control" formControlName="coverImageUrl" placeholder="https://" id="load-image" class="form-control">
|
||||
<button class="btn btn-outline-secondary" type="button" id="load-image-addon" (click)="loadImageFromUrl(); mode='all';" [disabled]="(form.get('coverImageUrl')?.value).length === 0">
|
||||
<input type="text" autofocus autocomplete="off" class="form-control" formControlName="coverImageUrl" placeholder="https://" id="load-image">
|
||||
<button class="btn btn-outline-secondary" type="button" id="load-image-addon"
|
||||
(click)="loadImageFromUrl(); mode='all';"
|
||||
[disabled]="(form.get('coverImageUrl')?.value).length === 0">
|
||||
{{t('load')}}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component, DestroyRef,
|
||||
EventEmitter, HostListener, inject,
|
||||
Component,
|
||||
DestroyRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
|
|
@ -18,7 +21,7 @@ import {SeriesService} from 'src/app/_services/series.service';
|
|||
import {ActionService} from 'src/app/_services/action.service';
|
||||
import {EditSeriesModalComponent} from '../_modals/edit-series-modal/edit-series-modal.component';
|
||||
import {RelationKind} from 'src/app/_models/series-detail/relation-kind';
|
||||
import {CommonModule} from "@angular/common";
|
||||
import {DecimalPipe} from "@angular/common";
|
||||
import {CardItemComponent} from "../card-item/card-item.component";
|
||||
import {RelationshipPipe} from "../../_pipes/relationship.pipe";
|
||||
import {Device} from "../../_models/device/device";
|
||||
|
|
@ -68,7 +71,9 @@ function deepClone(obj: any): any {
|
|||
@Component({
|
||||
selector: 'app-series-card',
|
||||
standalone: true,
|
||||
imports: [CommonModule, CardItemComponent, RelationshipPipe, CardActionablesComponent, DefaultValuePipe, DownloadIndicatorComponent, EntityTitleComponent, FormsModule, ImageComponent, NgbProgressbar, NgbTooltip, RouterLink, TranslocoDirective, SeriesFormatComponent],
|
||||
imports: [CardItemComponent, RelationshipPipe, CardActionablesComponent, DefaultValuePipe, DownloadIndicatorComponent,
|
||||
EntityTitleComponent, FormsModule, ImageComponent, NgbProgressbar, NgbTooltip, RouterLink, TranslocoDirective,
|
||||
SeriesFormatComponent, DecimalPipe],
|
||||
templateUrl: './series-card.component.html',
|
||||
styleUrls: ['./series-card.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
@ -264,6 +269,9 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
case Action.RemoveFromOnDeck:
|
||||
this.seriesService.removeFromOnDeck(series.id).subscribe(() => this.reload.emit(series.id));
|
||||
break;
|
||||
case Action.Download:
|
||||
this.downloadService.download('series', this.series);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ import {Volume} from "../../_models/volume";
|
|||
import {UtilityService} from "../../shared/_services/utility.service";
|
||||
import {LibraryType} from "../../_models/library/library";
|
||||
import {RelationshipPipe} from "../../_pipes/relationship.pipe";
|
||||
import {Device} from "../../_models/device/device";
|
||||
import {ActionService} from "../../_services/action.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-volume-card',
|
||||
|
|
@ -63,11 +65,11 @@ export class VolumeCardComponent implements OnInit {
|
|||
public readonly imageService = inject(ImageService);
|
||||
public readonly bulkSelectionService = inject(BulkSelectionService);
|
||||
private readonly downloadService = inject(DownloadService);
|
||||
private readonly actionService = inject(ActionService);
|
||||
private readonly messageHub = inject(MessageHubService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly actionFactoryService = inject(ActionFactoryService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly readerService = inject(ReaderService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
|
|
@ -196,6 +198,12 @@ export class VolumeCardComponent implements OnInit {
|
|||
return; // Don't propagate the download from a card
|
||||
}
|
||||
|
||||
if (action.action == Action.SendTo) {
|
||||
const device = (action._extra!.data as Device);
|
||||
this.actionService.sendToDevice(this.volume.chapters.map(c => c.id), device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action, this.volume);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@
|
|||
<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"></app-details-tab>
|
||||
<app-details-tab [metadata]="chapter" [genres]="chapter.genres" [tags]="chapter.tags" [webLinks]="weblinks"></app-details-tab>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ export class ChapterDetailComponent implements OnInit {
|
|||
series: Series | null = null;
|
||||
libraryType: LibraryType | null = null;
|
||||
hasReadingProgress = false;
|
||||
weblinks: Array<string> = [];
|
||||
activeTabId = TabID.Details;
|
||||
/**
|
||||
* This is the download we get from download service.
|
||||
|
|
@ -259,6 +260,7 @@ export class ChapterDetailComponent implements OnInit {
|
|||
|
||||
this.series = results.series;
|
||||
this.chapter = results.chapter;
|
||||
this.weblinks = this.chapter.webLinks.split(',');
|
||||
this.libraryType = results.libraryType;
|
||||
|
||||
this.themeService.setColorScape(this.chapter.primaryColor, this.chapter.secondaryColor);
|
||||
|
|
@ -281,7 +283,8 @@ export class ChapterDetailComponent implements OnInit {
|
|||
}
|
||||
}), takeUntilDestroyed(this.destroyRef)).subscribe();
|
||||
|
||||
this.showDetailsTab = hasAnyCast(this.chapter) || (this.chapter.genres || []).length > 0 || (this.chapter.tags || []).length > 0;
|
||||
this.showDetailsTab = hasAnyCast(this.chapter) || (this.chapter.genres || []).length > 0 ||
|
||||
(this.chapter.tags || []).length > 0 || this.chapter.webLinks.length > 0;
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -171,18 +171,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const navbarHeight = navbar.offsetHeight;
|
||||
const totalHeight = companionHeight + navbarHeight + 21; //21px to account for padding
|
||||
return 'calc(var(--vh)*100 - ' + totalHeight + 'px)';
|
||||
}
|
||||
|
||||
|
||||
constructor(@Inject(DOCUMENT) private document: Document) {
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
|
||||
|
|
@ -299,10 +287,16 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
}
|
||||
switch (action.action) {
|
||||
case Action.Promote:
|
||||
this.collectionService.promoteMultipleCollections([this.collectionTag.id], true).subscribe();
|
||||
this.collectionService.promoteMultipleCollections([this.collectionTag.id], true).subscribe(() => {
|
||||
this.collectionTag.promoted = true;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.UnPromote:
|
||||
this.collectionService.promoteMultipleCollections([this.collectionTag.id], false).subscribe();
|
||||
this.collectionService.promoteMultipleCollections([this.collectionTag.id], false).subscribe(() => {
|
||||
this.collectionTag.promoted = false;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case(Action.Edit):
|
||||
this.openEditCollectionTagModal(this.collectionTag);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
|||
import {CardItemComponent} from '../../cards/card-item/card-item.component';
|
||||
import {SeriesCardComponent} from '../../cards/series-card/series-card.component';
|
||||
import {CarouselReelComponent} from '../../carousel/_components/carousel-reel/carousel-reel.component';
|
||||
import {AsyncPipe, NgForOf, NgTemplateOutlet} from '@angular/common';
|
||||
import {AsyncPipe, NgTemplateOutlet} from '@angular/common';
|
||||
import {
|
||||
SideNavCompanionBarComponent
|
||||
} from '../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
||||
|
|
@ -51,7 +51,7 @@ enum StreamId {
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [SideNavCompanionBarComponent, RouterLink, CarouselReelComponent, SeriesCardComponent,
|
||||
CardItemComponent, AsyncPipe, TranslocoDirective, NgForOf, NgTemplateOutlet, LoadingComponent],
|
||||
CardItemComponent, AsyncPipe, TranslocoDirective, NgTemplateOutlet, LoadingComponent],
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@
|
|||
{{t('cover-image-description')}}
|
||||
</p>
|
||||
<app-cover-image-chooser [(imageUrls)]="imageUrls"
|
||||
(imageUrlsChange)="handleUploadByUrl($event)"
|
||||
(imageSelected)="updateSelectedIndex($event)"
|
||||
(selectedBase64Url)="updateSelectedImage($event)"
|
||||
[showReset]="person.coverImageLocked"
|
||||
|
|
|
|||
|
|
@ -102,7 +102,9 @@ export class EditPersonModalComponent implements OnInit {
|
|||
save() {
|
||||
const apis = [];
|
||||
|
||||
if (this.touchedCoverImage || this.coverImageReset) {
|
||||
const hasCoverChanges = this.touchedCoverImage || this.coverImageReset;
|
||||
|
||||
if (hasCoverChanges) {
|
||||
apis.push(this.uploadService.updatePersonCoverImage(this.person.id, this.selectedCover, !this.coverImageReset));
|
||||
}
|
||||
|
||||
|
|
@ -121,10 +123,16 @@ export class EditPersonModalComponent implements OnInit {
|
|||
apis.push(this.personService.updatePerson(person));
|
||||
|
||||
forkJoin(apis).subscribe(_ => {
|
||||
this.modal.close({success: true, coverImageUpdate: false, person: person});
|
||||
this.modal.close({success: true, coverImageUpdate: hasCoverChanges, person: person});
|
||||
});
|
||||
}
|
||||
|
||||
handleUploadByUrl(urls: Array<string>) {
|
||||
this.selectedCover = urls[0];
|
||||
this.touchedCoverImage = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateSelectedIndex(index: number) {
|
||||
this.editForm.patchValue({
|
||||
coverImageIndex: index
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import {BadgeExpanderComponent} from '../../../shared/badge-expander/badge-expan
|
|||
import {ReadMoreComponent} from '../../../shared/read-more/read-more.component';
|
||||
import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ImageComponent} from '../../../shared/image/image.component';
|
||||
import {AsyncPipe, DatePipe, DecimalPipe, NgClass, NgIf} from '@angular/common';
|
||||
import {AsyncPipe, DatePipe, DecimalPipe, NgClass} from '@angular/common';
|
||||
import {
|
||||
SideNavCompanionBarComponent
|
||||
} from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
||||
|
|
@ -174,7 +174,23 @@ export class ReadingListDetailComponent implements OnInit {
|
|||
this.readingList = rl;
|
||||
this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '<br>');
|
||||
this.cdRef.markForCheck();
|
||||
})
|
||||
});
|
||||
});
|
||||
break;
|
||||
case Action.Promote:
|
||||
this.actionService.promoteMultipleReadingLists([this.readingList!], true, () => {
|
||||
if (this.readingList) {
|
||||
this.readingList.promoted = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case Action.UnPromote:
|
||||
this.actionService.promoteMultipleReadingLists([this.readingList!], false, () => {
|
||||
if (this.readingList) {
|
||||
this.readingList.promoted = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -438,8 +438,9 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
}
|
||||
|
||||
if (this.currentlyReadingChapter.minNumber === LooseLeafOrDefaultNumber) {
|
||||
return translate(chapterLocaleKey, {num: vol[0].minNumber});
|
||||
return translate(volumeLocaleKey, {num: vol[0].minNumber});
|
||||
}
|
||||
|
||||
return translate(volumeLocaleKey, {num: vol[0].minNumber})
|
||||
+ ' ' + translate(chapterLocaleKey, {num: this.currentlyReadingChapter.minNumber});
|
||||
}
|
||||
|
|
@ -872,7 +873,8 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
this.showVolumeTab = this.shouldShowVolumeTab();
|
||||
this.showStorylineTab = this.shouldShowStorylineTab();
|
||||
this.showChapterTab = this.shouldShowChaptersTab();
|
||||
this.showDetailsTab = hasAnyCast(this.seriesMetadata) || (this.seriesMetadata?.genres || []).length > 0 || (this.seriesMetadata?.tags || []).length > 0;
|
||||
this.showDetailsTab = hasAnyCast(this.seriesMetadata) || (this.seriesMetadata?.genres || []).length > 0
|
||||
|| (this.seriesMetadata?.tags || []).length > 0 || (this.seriesMetadata?.webLinks || []).length > 0;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="row g-0">
|
||||
<div class="col-10">
|
||||
<h6 class="section-title">
|
||||
@if(labelId) {
|
||||
@if (labelId) {
|
||||
<label class="reset-label" [for]="labelId">{{title}}</label>
|
||||
} @else {
|
||||
{{title}}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ export class SettingItemComponent {
|
|||
toggleEditMode() {
|
||||
|
||||
if (!this.toggleOnViewClick) return;
|
||||
if (!this.canEdit) return;
|
||||
|
||||
this.isEditMode = !this.isEditMode;
|
||||
this.editMode.emit(this.isEditMode);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<ng-container *transloco="let t; read:'change-age-restriction'">
|
||||
@if (user) {
|
||||
<app-setting-item [title]="t('age-restriction-label')">
|
||||
<app-setting-item [title]="t('age-restriction-label')" [canEdit]="accountService.hasChangeAgeRestrictionRole(user) || accountService.hasAdminRole(user)">
|
||||
<ng-template #view>
|
||||
<span class="col-12">{{user.ageRestriction.ageRating | ageRating }}
|
||||
<span class="col-12" [ngClass]="{'disabled': !accountService.hasChangeAgeRestrictionRole(user) && !accountService.hasAdminRole(user)}">{{user.ageRestriction.ageRating | ageRating }}
|
||||
@if (user.ageRestriction.ageRating !== AgeRating.NotApplicable && user.ageRestriction.includeUnknowns) {
|
||||
<span class="ms-1 me-1">+</span> {{t('unknowns')}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
.disabled {
|
||||
color: var(--btn-disabled-text-color);
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
|||
import { AgeRatingPipe } from '../../_pipes/age-rating.pipe';
|
||||
import { RestrictionSelectorComponent } from '../restriction-selector/restriction-selector.component';
|
||||
import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {AsyncPipe, NgForOf, NgIf} from '@angular/common';
|
||||
import {AsyncPipe, NgClass, NgForOf, NgIf} from '@angular/common';
|
||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {SettingTitleComponent} from "../../settings/_components/setting-title/setting-title.component";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
|
|
@ -29,11 +29,12 @@ import {SettingItemComponent} from "../../settings/_components/setting-item/sett
|
|||
styleUrls: ['./change-age-restriction.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgbCollapse, RestrictionSelectorComponent, AsyncPipe, AgeRatingPipe, TranslocoDirective, SettingTitleComponent, NgForOf, NgIf, ReactiveFormsModule, Select2Module, SettingItemComponent]
|
||||
imports: [NgbCollapse, RestrictionSelectorComponent, AsyncPipe, AgeRatingPipe, TranslocoDirective, SettingTitleComponent,
|
||||
ReactiveFormsModule, SettingItemComponent, NgClass]
|
||||
})
|
||||
export class ChangeAgeRestrictionComponent implements OnInit {
|
||||
|
||||
private readonly accountService = inject(AccountService);
|
||||
protected readonly accountService = inject(AccountService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
|
@ -64,6 +65,8 @@ export class ChangeAgeRestrictionComponent implements OnInit {
|
|||
|
||||
updateRestrictionSelection(restriction: AgeRestriction) {
|
||||
this.selectedRestriction = restriction;
|
||||
|
||||
this.saveForm();
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue