Reading List Detail Overhaul + More Bugfixes and Polish (#3687)

Co-authored-by: Yongun Seong <yseong.p@gmail.com>
This commit is contained in:
Joe Milazzo 2025-03-29 19:47:53 -05:00 committed by GitHub
parent b2ee651fb8
commit dad212bfb9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 5056 additions and 729 deletions

View file

@ -28,12 +28,24 @@
</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 }}
@if (ageRating) {
<div class="mb-3 ms-1">
<h4 class="header">{{t('age-rating-title')}}</h4>
<div class="ms-3">
<app-age-rating-image [rating]="ageRating" />
</div>
</div>
</div>
}
@if (format) {
<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>

View file

@ -20,6 +20,8 @@ import {MangaFormatPipe} from "../../_pipes/manga-format.pipe";
import {LanguageNamePipe} from "../../_pipes/language-name.pipe";
import {AsyncPipe} from "@angular/common";
import {SafeUrlPipe} from "../../_pipes/safe-url.pipe";
import {AgeRating} from "../../_models/metadata/age-rating";
import {AgeRatingImageComponent} from "../age-rating-image/age-rating-image.component";
@Component({
selector: 'app-details-tab',
@ -34,7 +36,8 @@ import {SafeUrlPipe} from "../../_pipes/safe-url.pipe";
MangaFormatPipe,
LanguageNamePipe,
AsyncPipe,
SafeUrlPipe
SafeUrlPipe,
AgeRatingImageComponent
],
templateUrl: './details-tab.component.html',
styleUrl: './details-tab.component.scss',
@ -47,11 +50,13 @@ export class DetailsTabComponent {
protected readonly PersonRole = PersonRole;
protected readonly FilterField = FilterField;
protected readonly MangaFormat = MangaFormat;
@Input({required: true}) metadata!: IHasCast;
@Input() readingTime: IHasReadingTime | undefined;
@Input() ageRating: AgeRating | undefined;
@Input() language: string | undefined;
@Input() format: MangaFormat = MangaFormat.UNKNOWN;
@Input() format: MangaFormat | undefined;
@Input() releaseYear: number | undefined;
@Input() genres: Array<Genre> = [];
@Input() tags: Array<Tag> = [];
@ -62,6 +67,4 @@ export class DetailsTabComponent {
if (queryParamName === FilterField.None) return;
this.filterUtilityService.applyFilter(['all-series'], queryParamName, FilterComparison.Equal, `${filter}`).subscribe();
}
protected readonly MangaFormat = MangaFormat;
}

View file

@ -8,7 +8,7 @@
</div>
<div class="ms-1">
<div><span class="title">{{item.series.name}}</span> <span class="me-1 float-end">({{item.matchRating | translocoPercent}})</span></div>
<div class="text-body-secondary">
<div class="text-muted">
@for(synm of item.series.synonyms; track synm; let last = $last) {
{{synm}}
@if (!last) {

View file

@ -8,9 +8,7 @@ import {
Output
} from '@angular/core';
import {ImageComponent} from "../../shared/image/image.component";
import {SeriesFormatComponent} from "../../shared/series-format/series-format.component";
import {ExternalSeriesMatch} from "../../_models/series-detail/external-series-match";
import {PercentPipe} from "@angular/common";
import {TranslocoPercentPipe} from "@jsverse/transloco-locale";
import {ReadMoreComponent} from "../../shared/read-more/read-more.component";
import {TranslocoDirective} from "@jsverse/transloco";
@ -18,18 +16,18 @@ import {PlusMediaFormatPipe} from "../../_pipes/plus-media-format.pipe";
import {LoadingComponent} from "../../shared/loading/loading.component";
@Component({
selector: 'app-match-series-result-item',
imports: [
ImageComponent,
TranslocoPercentPipe,
ReadMoreComponent,
TranslocoDirective,
PlusMediaFormatPipe,
LoadingComponent
],
templateUrl: './match-series-result-item.component.html',
styleUrl: './match-series-result-item.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
selector: 'app-match-series-result-item',
imports: [
ImageComponent,
TranslocoPercentPipe,
ReadMoreComponent,
TranslocoDirective,
PlusMediaFormatPipe,
LoadingComponent
],
templateUrl: './match-series-result-item.component.html',
styleUrl: './match-series-result-item.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MatchSeriesResultItemComponent {

View file

@ -35,6 +35,7 @@
[count]="pageInfo.totalElements"
[offset]="pageInfo.pageNumber"
[limit]="pageInfo.size"
[sorts]="[{prop: 'lastModifiedUtc', dir: 'desc'}]"
>
<ngx-datatable-column prop="lastModifiedUtc" [sortable]="true" [draggable]="false" [resizeable]="false">

View file

@ -10,7 +10,7 @@ import {debounceTime, take} from "rxjs/operators";
import {PaginatedResult} from "../../_models/pagination";
import {SortEvent} from "../table/_directives/sortable-header.directive";
import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";
import {TranslocoModule} from "@jsverse/transloco";
import {translate, TranslocoModule} from "@jsverse/transloco";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {TranslocoLocaleModule} from "@jsverse/transloco-locale";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
@ -18,6 +18,7 @@ import {LooseLeafOrDefaultNumber, SpecialVolumeNumber} from "../../_models/chapt
import {ColumnMode, NgxDatatableModule} from "@siemens/ngx-datatable";
import {AsyncPipe} from "@angular/common";
import {AccountService} from "../../_services/account.service";
import {ToastrService} from "ngx-toastr";
export interface DataTablePage {
pageNumber: number,
@ -44,6 +45,7 @@ export class UserScrobbleHistoryComponent implements OnInit {
private readonly scrobblingService = inject(ScrobblingService);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly destroyRef = inject(DestroyRef);
private readonly toastr = inject(ToastrService);
protected readonly accountService = inject(AccountService);
@ -60,6 +62,10 @@ export class UserScrobbleHistoryComponent implements OnInit {
totalElements: 0,
totalPages: 0
}
private currentSort: SortEvent<ScrobbleEvent> = {
column: 'lastModifiedUtc',
direction: 'desc'
};
ngOnInit() {
@ -73,26 +79,26 @@ export class UserScrobbleHistoryComponent implements OnInit {
this.formGroup.get('filter')?.valueChanges.pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe(query => {
this.loadPage();
})
});
this.loadPage(this.currentSort);
}
onPageChange(pageInfo: any) {
this.pageInfo.pageNumber = pageInfo.offset;
this.cdRef.markForCheck();
this.loadPage();
this.loadPage(this.currentSort);
}
updateSort(data: any) {
this.loadPage({column: data.column.prop, direction: data.newValue});
this.currentSort = {
column: data.column.prop,
direction: data.newValue
};
}
loadPage(sortEvent?: SortEvent<ScrobbleEvent>) {
if (sortEvent && this.pageInfo) {
this.pageInfo.pageNumber = 1;
this.cdRef.markForCheck();
}
const page = (this.pageInfo?.pageNumber || 0) + 1;
const pageSize = this.pageInfo?.size || 0;
const isDescending = sortEvent?.direction === 'desc';
@ -102,7 +108,6 @@ export class UserScrobbleHistoryComponent implements OnInit {
this.isLoading = true;
this.cdRef.markForCheck();
// BUG: Table should be sorted by lastModifiedUtc by default
this.scrobblingService.getScrobbleEvents({query, field, isDescending}, page, pageSize)
.pipe(take(1))
.subscribe((result: PaginatedResult<ScrobbleEvent[]>) => {
@ -122,13 +127,14 @@ export class UserScrobbleHistoryComponent implements OnInit {
case 'isProcessed': return ScrobbleEventSortField.IsProcessed;
case 'lastModifiedUtc': return ScrobbleEventSortField.LastModified;
case 'seriesName': return ScrobbleEventSortField.Series;
case 'scrobbleEventType': return ScrobbleEventSortField.ScrobbleEvent;
}
return ScrobbleEventSortField.None;
}
generateScrobbleEvents() {
this.scrobblingService.triggerScrobbleEventGeneration().subscribe(_ => {
this.toastr.info(translate('toasts.scrobble-gen-init'))
});
}
}