This commit is contained in:
Joe Milazzo 2024-11-14 07:11:39 -06:00 committed by GitHub
parent 2d5a7a3606
commit 7c4d7dc821
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 182 additions and 245 deletions

View File

@ -180,13 +180,6 @@ public class SettingsController : BaseApiController
_unitOfWork.SettingsRepository.Update(setting);
}
if (setting.Key == ServerSettingKey.CoverImageSize &&
updateSettingsDto.CoverImageSize + string.Empty != setting.Value)
{
setting.Value = updateSettingsDto.CoverImageSize + string.Empty;
_unitOfWork.SettingsRepository.Update(setting);
}
if (setting.Key == ServerSettingKey.Port && updateSettingsDto.Port + string.Empty != setting.Value)
{
if (OsInfo.IsDocker) continue;
@ -260,9 +253,16 @@ public class SettingsController : BaseApiController
}
if (setting.Key == ServerSettingKey.EncodeMediaAs &&
updateSettingsDto.EncodeMediaAs + string.Empty != setting.Value)
((int)updateSettingsDto.EncodeMediaAs).ToString() != setting.Value)
{
setting.Value = updateSettingsDto.EncodeMediaAs + string.Empty;
setting.Value = ((int)updateSettingsDto.EncodeMediaAs).ToString();
_unitOfWork.SettingsRepository.Update(setting);
}
if (setting.Key == ServerSettingKey.CoverImageSize &&
((int)updateSettingsDto.CoverImageSize).ToString() != setting.Value)
{
setting.Value = ((int)updateSettingsDto.CoverImageSize).ToString();
_unitOfWork.SettingsRepository.Update(setting);
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Entities;
using API.Entities.Enums;
using Flurl.Util;
using Kavita.Common.EnvironmentInfo;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data.ManualMigrations;
/// <summary>
/// At some point, encoding settings wrote bad data to the backend, maybe in v0.8.0. This just fixes any bad data.
/// </summary>
public static class ManualMigrateEncodeSettings
{
public static async Task Migrate(DataContext context, ILogger<Program> logger)
{
if (await context.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateEncodeSettings"))
{
return;
}
logger.LogCritical("Running ManualMigrateEncodeSettings migration - Please be patient, this may take some time. This is not an error");
var encodeAs = await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.EncodeMediaAs);
var coverSize = await context.ServerSetting.FirstAsync(s => s.Key == ServerSettingKey.CoverImageSize);
var encodeMap = new Dictionary<string, string>
{
{ EncodeFormat.WEBP.ToString(), ((int)EncodeFormat.WEBP).ToString() },
{ EncodeFormat.PNG.ToString(), ((int)EncodeFormat.PNG).ToString() },
{ EncodeFormat.AVIF.ToString(), ((int)EncodeFormat.AVIF).ToString() }
};
if (encodeMap.TryGetValue(encodeAs.Value, out var encodedValue))
{
encodeAs.Value = encodedValue;
context.ServerSetting.Update(encodeAs);
}
if (coverSize.Value == "0")
{
coverSize.Value = ((int)CoverImageSize.Default).ToString();
context.ServerSetting.Update(coverSize);
}
if (context.ChangeTracker.HasChanges())
{
await context.SaveChangesAsync();
}
await context.ManualMigrationHistory.AddAsync(new ManualMigrationHistory()
{
Name = "ManualMigrateEncodeSettings",
ProductVersion = BuildInfo.Version.ToString(),
RanAt = DateTime.UtcNow
});
await context.SaveChangesAsync();
logger.LogCritical("Running ManualMigrateEncodeSettings migration - Completed. This is not an error");
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using API.DTOs.Settings;
using API.Entities;
using API.Entities.Enums;
@ -33,7 +34,7 @@ public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>,
destination.LoggingLevel = row.Value;
break;
case ServerSettingKey.Port:
destination.Port = int.Parse(row.Value);
destination.Port = int.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.IpAddresses:
destination.IpAddresses = row.Value;
@ -53,11 +54,8 @@ public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>,
case ServerSettingKey.InstallVersion:
destination.InstallVersion = row.Value;
break;
case ServerSettingKey.EncodeMediaAs:
destination.EncodeMediaAs = Enum.Parse<EncodeFormat>(row.Value);
break;
case ServerSettingKey.TotalBackups:
destination.TotalBackups = int.Parse(row.Value);
destination.TotalBackups = int.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.InstallId:
destination.InstallId = row.Value;
@ -66,33 +64,36 @@ public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>,
destination.EnableFolderWatching = bool.Parse(row.Value);
break;
case ServerSettingKey.TotalLogs:
destination.TotalLogs = int.Parse(row.Value);
destination.TotalLogs = int.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.HostName:
destination.HostName = row.Value;
break;
case ServerSettingKey.CacheSize:
destination.CacheSize = long.Parse(row.Value);
destination.CacheSize = long.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.OnDeckProgressDays:
destination.OnDeckProgressDays = int.Parse(row.Value);
destination.OnDeckProgressDays = int.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.OnDeckUpdateDays:
destination.OnDeckUpdateDays = int.Parse(row.Value);
destination.OnDeckUpdateDays = int.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.CoverImageSize:
destination.CoverImageSize = Enum.Parse<CoverImageSize>(row.Value);
break;
case ServerSettingKey.EncodeMediaAs:
destination.EncodeMediaAs = Enum.Parse<EncodeFormat>(row.Value);
break;
case ServerSettingKey.BackupDirectory:
destination.BookmarksDirectory = row.Value;
break;
case ServerSettingKey.EmailHost:
destination.SmtpConfig ??= new SmtpConfigDto();
destination.SmtpConfig.Host = row.Value;
destination.SmtpConfig.Host = row.Value ?? string.Empty;
break;
case ServerSettingKey.EmailPort:
destination.SmtpConfig ??= new SmtpConfigDto();
destination.SmtpConfig.Port = string.IsNullOrEmpty(row.Value) ? 0 : int.Parse(row.Value);
destination.SmtpConfig.Port = string.IsNullOrEmpty(row.Value) ? 0 : int.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.EmailAuthPassword:
destination.SmtpConfig ??= new SmtpConfigDto();
@ -116,18 +117,22 @@ public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>,
break;
case ServerSettingKey.EmailSizeLimit:
destination.SmtpConfig ??= new SmtpConfigDto();
destination.SmtpConfig.SizeLimit = int.Parse(row.Value);
destination.SmtpConfig.SizeLimit = int.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.EmailCustomizedTemplates:
destination.SmtpConfig ??= new SmtpConfigDto();
destination.SmtpConfig.CustomizedTemplates = bool.Parse(row.Value);
break;
case ServerSettingKey.FirstInstallDate:
destination.FirstInstallDate = DateTime.Parse(row.Value);
destination.FirstInstallDate = DateTime.Parse(row.Value, CultureInfo.InvariantCulture);
break;
case ServerSettingKey.FirstInstallVersion:
destination.FirstInstallVersion = row.Value;
break;
case ServerSettingKey.LicenseKey:
break;
default:
throw new ArgumentOutOfRangeException();
}
}

View File

@ -127,9 +127,11 @@ public class ArchiveService : IArchiveService
}
case ArchiveLibrary.NotSupported:
_logger.LogWarning("[GetNumberOfPagesFromArchive] This archive cannot be read: {ArchivePath}. Defaulting to 0 pages", archivePath);
_mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService, "File format not supported", string.Empty);
return 0;
default:
_logger.LogWarning("[GetNumberOfPagesFromArchive] There was an exception when reading archive stream: {ArchivePath}. Defaulting to 0 pages", archivePath);
_mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService, "File format not supported", string.Empty);
return 0;
}
}

View File

@ -362,6 +362,7 @@ public class MetadataService : IMetadataService
return;
}
// TODO: Cache this because it's called a lot during scans
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
var encodeFormat = settings.EncodeMediaAs;
var coverImageSize = settings.CoverImageSize;

View File

@ -99,6 +99,7 @@ public class ReadingItemService : IReadingItemService
/// <returns></returns>
public int GetNumberOfPages(string filePath, MangaFormat format)
{
switch (format)
{
case MangaFormat.Archive:

View File

@ -276,6 +276,7 @@ public class Startup
await ManualMigrateRemovePeople.Migrate(dataContext, logger);
await MigrateDuplicateDarkTheme.Migrate(dataContext, logger);
await ManualMigrateUnscrobbleBookLibraries.Migrate(dataContext, logger);
await ManualMigrateEncodeSettings.Migrate(dataContext, logger);
// Update the version in the DB after all migrations are run
var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);

View File

@ -7,9 +7,9 @@
<div class="col-auto">
@if(!isLoading && allLibraries.length > 0) {
<span class="form-check float-end">
<input id="select-all" type="checkbox" class="form-check-input"
<input id="lib--select-all" type="checkbox" class="form-check-input"
[ngModel]="selectAll" (change)="toggleAll()" [indeterminate]="hasSomeSelected">
<label for="select-all" class="form-check-label">{{selectAll ? t('deselect-all') : t('select-all')}}</label>
<label for="lib--select-all" class="form-check-label">{{selectAll ? t('deselect-all') : t('select-all')}}</label>
</span>
}
</div>

View File

@ -4,29 +4,20 @@ import {ToastrService} from 'ngx-toastr';
import {debounceTime, distinctUntilChanged, filter, switchMap, take, tap} from 'rxjs';
import {SettingsService} from '../settings.service';
import {ServerSettings} from '../_models/server-settings';
import {
NgbAlert,
NgbTooltip
} from '@ng-bootstrap/ng-bootstrap';
import {AsyncPipe, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule} from "@jsverse/transloco";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {ManageMediaIssuesComponent} from "../manage-media-issues/manage-media-issues.component";
import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component";
import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {BytesPipe} from "../../_pipes/bytes.pipe";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
@Component({
selector: 'app-manage-email-settings',
templateUrl: './manage-email-settings.component.html',
styleUrls: ['./manage-email-settings.component.scss'],
standalone: true,
selector: 'app-manage-email-settings',
templateUrl: './manage-email-settings.component.html',
styleUrls: ['./manage-email-settings.component.scss'],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, NgbTooltip, NgTemplateOutlet, TranslocoModule, SafeHtmlPipe,
ManageMediaIssuesComponent, TitleCasePipe, NgbAlert, SettingItemComponent, SettingSwitchComponent, DefaultValuePipe, BytesPipe, AsyncPipe, CardActionablesComponent]
imports: [ReactiveFormsModule, TranslocoModule, SettingItemComponent, SettingSwitchComponent, DefaultValuePipe, BytesPipe]
})
export class ManageEmailSettingsComponent implements OnInit {
@ -55,8 +46,8 @@ export class ManageEmailSettingsComponent implements OnInit {
// Automatically save settings as we edit them
this.settingsForm.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
debounceTime(100),
filter(_ => this.settingsForm.valid),
takeUntilDestroyed(this.destroyRef),
switchMap(_ => {
@ -65,7 +56,6 @@ export class ManageEmailSettingsComponent implements OnInit {
}),
tap(settings => {
this.serverSettings = settings;
this.resetForm();
this.cdRef.markForCheck();
})
).subscribe();

View File

@ -6,9 +6,9 @@
<div class="col-auto">
@if(selectedRoles.length > 0) {
<span class="form-check float-end">
<input id="select-all" type="checkbox" class="form-check-input"
<input id="role--select-all" type="checkbox" class="form-check-input"
[ngModel]="selectAll" (change)="toggleAll()" [indeterminate]="hasSomeSelected">
<label for="select-all" class="form-check-label">{{selectAll ? t('deselect-all') : t('select-all')}}</label>
<label for="role--select-all" class="form-check-label">{{selectAll ? t('deselect-all') : t('select-all')}}</label>
</span>
}
</div>

View File

@ -133,21 +133,6 @@ export class AllSeriesComponent implements OnInit {
});
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
updateFilter(data: FilterEvent) {
if (data.filterV2 === undefined) return;
this.filter = data.filterV2;

View File

@ -100,20 +100,6 @@ export class BookmarksComponent implements OnInit {
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
async handleAction(action: ActionItem<Series>, series: Series) {
switch (action.action) {
case(Action.Delete):

View File

@ -2,7 +2,7 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
DestroyRef, HostListener,
inject,
Input,
OnInit
@ -14,6 +14,7 @@ import {AsyncPipe, DecimalPipe, NgStyle} from "@angular/common";
import {TranslocoModule} from "@jsverse/transloco";
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
import {KEY_CODES} from "../../shared/_services/utility.service";
@Component({
selector: 'app-bulk-operations',
@ -55,7 +56,20 @@ export class BulkOperationsComponent implements OnInit {
public readonly bulkSelectionService = inject(BulkSelectionService);
protected readonly Action = Action;
constructor() { }
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
// TODO: See if we can figure out a select all (Ctrl+A) by having each method handle the event or pass all the data into this component.
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
ngOnInit(): void {
this.bulkSelectionService.actions$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(actions => {

View File

@ -81,20 +81,6 @@ export class AllCollectionsComponent implements OnInit {
trackByIdentity = (index: number, item: UserCollection) => `${item.id}_${item.title}_${item.owner}_${item.promoted}`;
user!: User;
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
constructor() {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;

View File

@ -234,20 +234,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
this.scrollService.setScrollContainer(this.scrollingBlock);
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
updateTag(tagId: number) {
this.collectionService.allCollections().subscribe(tags => {
const matchingTags = tags.filter(t => t.id === tagId);

View File

@ -29,12 +29,10 @@ import {FilterSettings} from '../metadata-filter/filter-settings';
import {JumpKey} from '../_models/jumpbar/jump-key';
import {SeriesRemovedEvent} from '../_models/events/series-removed-event';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {SentenceCasePipe} from '../_pipes/sentence-case.pipe';
import {BulkOperationsComponent} from '../cards/bulk-operations/bulk-operations.component';
import {SeriesCardComponent} from '../cards/series-card/series-card.component';
import {CardDetailLayoutComponent} from '../cards/card-detail-layout/card-detail-layout.component';
import {DecimalPipe} from '@angular/common';
import {NgbNav, NgbNavContent, NgbNavItem, NgbNavItemRole, NgbNavLink, NgbNavOutlet} from '@ng-bootstrap/ng-bootstrap';
import {
SideNavCompanionBarComponent
} from '../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
@ -52,8 +50,8 @@ import {debounceTime, ReplaySubject, tap} from "rxjs";
styleUrls: ['./library-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SideNavCompanionBarComponent, CardActionablesComponent, NgbNav, NgbNavItem, NgbNavItemRole, NgbNavLink, NgbNavContent,
CardDetailLayoutComponent, SeriesCardComponent, BulkOperationsComponent, NgbNavOutlet, DecimalPipe, SentenceCasePipe, TranslocoDirective, LoadingComponent]
imports: [SideNavCompanionBarComponent, CardActionablesComponent,
CardDetailLayoutComponent, SeriesCardComponent, BulkOperationsComponent, DecimalPipe, TranslocoDirective, LoadingComponent]
})
export class LibraryDetailComponent implements OnInit {

View File

@ -11,7 +11,7 @@ import {
} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {FilterStatement} from '../../../_models/metadata/v2/filter-statement';
import {BehaviorSubject, distinctUntilChanged, filter, map, Observable, of, startWith, switchMap} from 'rxjs';
import {BehaviorSubject, distinctUntilChanged, filter, map, Observable, of, startWith, switchMap, tap} from 'rxjs';
import {MetadataService} from 'src/app/_services/metadata.service';
import {mangaFormatFilters} from 'src/app/_models/metadata/series-filter';
import {PersonRole} from 'src/app/_models/metadata/person';
@ -135,11 +135,8 @@ const BooleanComparisons = [
FilterFieldPipe,
FilterComparisonPipe,
Select2Module,
NgTemplateOutlet,
TagBadgeComponent,
NgbTooltip,
TranslocoDirective,
NgbDatepicker,
NgbInputDatepicker
],
changeDetection: ChangeDetectionStrategy.OnPush
@ -207,9 +204,11 @@ export class MetadataFilterRowComponent implements OnInit {
);
this.formGroup!.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(_ => {
this.propagateFilterUpdate();
});
this.formGroup!.valueChanges.pipe(
distinctUntilChanged(),
tap(_ => this.propagateFilterUpdate()),
takeUntilDestroyed(this.destroyRef)
).subscribe();
this.loaded = true;
this.cdRef.markForCheck();
@ -336,13 +335,13 @@ export class MetadataFilterRowComponent implements OnInit {
if (StringFields.includes(inputVal)) {
const comps = [...StringComparisons];
let comps = [...StringComparisons];
if (FieldsThatShouldIncludeIsEmpty.includes(inputVal)) {
comps.push(FilterComparison.IsEmpty);
}
this.validComparisons$.next(comps);
this.validComparisons$.next([...new Set(comps)]);
this.predicateType$.next(PredicateType.Text);
if (this.loaded) {
@ -362,7 +361,7 @@ export class MetadataFilterRowComponent implements OnInit {
comps.push(FilterComparison.IsEmpty);
}
this.validComparisons$.next(comps);
this.validComparisons$.next([...new Set(comps)]);
this.predicateType$.next(PredicateType.Number);
if (this.loaded) {
this.formGroup.get('filterValue')?.patchValue(0);
@ -377,7 +376,7 @@ export class MetadataFilterRowComponent implements OnInit {
comps.push(FilterComparison.IsEmpty);
}
this.validComparisons$.next(comps);
this.validComparisons$.next([...new Set(comps)]);
this.predicateType$.next(PredicateType.Date);
if (this.loaded) {
@ -388,12 +387,13 @@ export class MetadataFilterRowComponent implements OnInit {
}
if (BooleanFields.includes(inputVal)) {
const comps = [...DateComparisons];
let comps = [...DateComparisons];
if (FieldsThatShouldIncludeIsEmpty.includes(inputVal)) {
comps.push(FilterComparison.IsEmpty);
}
this.validComparisons$.next(comps);
this.validComparisons$.next([...new Set(comps)]);
this.predicateType$.next(PredicateType.Boolean);
if (this.loaded) {
@ -415,7 +415,7 @@ export class MetadataFilterRowComponent implements OnInit {
comps.push(FilterComparison.IsEmpty);
}
this.validComparisons$.next(comps);
this.validComparisons$.next([...new Set(comps)]);
this.predicateType$.next(PredicateType.Dropdown);
if (this.loaded) {
this.formGroup.get('filterValue')?.patchValue(0);

View File

@ -49,19 +49,6 @@ export class ReadingListsComponent implements OnInit {
globalActions: Array<ActionItem<any>> = [];
trackByIdentity = (index: number, item: ReadingList) => `${item.id}_${item.title}_${item.promoted}`;
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
constructor(private readingListService: ReadingListService, public imageService: ImageService, private actionFactoryService: ActionFactoryService,
private accountService: AccountService, private toastr: ToastrService, private router: Router,

View File

@ -2,11 +2,9 @@ import {
ChangeDetectionStrategy, ChangeDetectorRef,
Component,
DestroyRef,
EventEmitter,
inject,
Input,
OnInit,
Output
} from '@angular/core';
import {AsyncPipe} from "@angular/common";
import {Observable, shareReplay, tap} from "rxjs";

View File

@ -10,7 +10,7 @@ import {
import {SeriesService} from "../../../_services/series.service";
import {Rating} from "../../../_models/rating";
import {ProviderImagePipe} from "../../../_pipes/provider-image.pipe";
import {NgbModal, NgbPopover, NgbRating} from "@ng-bootstrap/ng-bootstrap";
import {NgbModal, NgbPopover} from "@ng-bootstrap/ng-bootstrap";
import {LoadingComponent} from "../../../shared/loading/loading.component";
import {LibraryType} from "../../../_models/library/library";
import {ProviderNamePipe} from "../../../_pipes/provider-name.pipe";
@ -22,13 +22,13 @@ import {TranslocoDirective} from "@jsverse/transloco";
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
import {ImageService} from "../../../_services/image.service";
import {AsyncPipe, NgOptimizedImage, NgTemplateOutlet} from "@angular/common";
import {InviteUserComponent} from "../../../admin/invite-user/invite-user.component";
import {RatingModalComponent} from "../rating-modal/rating-modal.component";
@Component({
selector: 'app-external-rating',
standalone: true,
imports: [ProviderImagePipe, NgbRating, NgbPopover, LoadingComponent, ProviderNamePipe, NgxStarsModule, ImageComponent, TranslocoDirective, SafeHtmlPipe, NgOptimizedImage, AsyncPipe, NgTemplateOutlet],
imports: [ProviderImagePipe, NgbPopover, LoadingComponent, ProviderNamePipe, NgxStarsModule, ImageComponent,
TranslocoDirective, SafeHtmlPipe, NgOptimizedImage, AsyncPipe, NgTemplateOutlet],
templateUrl: './external-rating.component.html',
styleUrls: ['./external-rating.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,

View File

@ -15,9 +15,7 @@ import {ImageService} from "../../../_services/image.service";
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
import {FilterField} from "../../../_models/metadata/v2/filter-field";
import {MangaFormatPipe} from "../../../_pipes/manga-format.pipe";
import {MangaFormat} from "../../../_models/manga-format";
import {MangaFormatIconPipe} from "../../../_pipes/manga-format-icon.pipe";
import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component";
import {PublisherFlipperComponent} from "../../../_single-modules/publisher-flipper/publisher-flipper.component";
@ -32,8 +30,6 @@ import {PublisherFlipperComponent} from "../../../_single-modules/publisher-flip
NgbTooltip,
TranslocoDirective,
ImageComponent,
MangaFormatPipe,
MangaFormatIconPipe,
SeriesFormatComponent,
PublisherFlipperComponent
],

View File

@ -1,5 +1,5 @@
import {ChangeDetectionStrategy, Component, ContentChild, inject, Input, TemplateRef} from '@angular/core';
import {CommonModule, NgTemplateOutlet} from '@angular/common';
import {NgTemplateOutlet} from '@angular/common';
import {A11yClickDirective} from "../../../shared/a11y-click.directive";
import {BadgeExpanderComponent} from "../../../shared/badge-expander/badge-expander.component";
import {TagBadgeComponent, TagBadgeCursor} from "../../../shared/tag-badge/tag-badge.component";

View File

@ -1,11 +1,8 @@
import {
AsyncPipe,
DecimalPipe,
DOCUMENT,
JsonPipe,
Location,
NgClass,
NgOptimizedImage,
NgStyle,
NgTemplateOutlet
} from '@angular/common';
@ -37,7 +34,6 @@ import {
NgbNavItem,
NgbNavLink,
NgbNavOutlet,
NgbProgressbar,
NgbTooltip
} from '@ng-bootstrap/ng-bootstrap';
import {ToastrService} from 'ngx-toastr';
@ -81,30 +77,20 @@ import {
import {PageLayoutMode} from 'src/app/_models/page-layout-mode';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {UserReview} from "../../../_single-module/review-card/user-review";
import {LoadingComponent} from '../../../shared/loading/loading.component';
import {ExternalSeriesCardComponent} from '../../../cards/external-series-card/external-series-card.component';
import {SeriesCardComponent} from '../../../cards/series-card/series-card.component';
import {EntityTitleComponent} from '../../../cards/entity-title/entity-title.component';
import {CardItemComponent} from '../../../cards/card-item/card-item.component';
import {VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
import {BulkOperationsComponent} from '../../../cards/bulk-operations/bulk-operations.component';
import {ReviewCardComponent} from '../../../_single-module/review-card/review-card.component';
import {CarouselReelComponent} from '../../../carousel/_components/carousel-reel/carousel-reel.component';
import {ImageComponent} from '../../../shared/image/image.component';
import {TagBadgeComponent} from '../../../shared/tag-badge/tag-badge.component';
import {
SideNavCompanionBarComponent
} from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
import {PublicationStatus} from "../../../_models/metadata/publication-status";
import {NextExpectedChapter} from "../../../_models/series-detail/next-expected-chapter";
import {NextExpectedCardComponent} from "../../../cards/next-expected-card/next-expected-card.component";
import {ProviderImagePipe} from "../../../_pipes/provider-image.pipe";
import {MetadataService} from "../../../_services/metadata.service";
import {Rating} from "../../../_models/rating";
import {ThemeService} from "../../../_services/theme.service";
import {PersonBadgeComponent} from "../../../shared/person-badge/person-badge.component";
import {DetailsTabComponent} from "../../../_single-module/details-tab/details-tab.component";
import {
EditChapterModalCloseResult,
@ -116,23 +102,14 @@ import {VolumeCardComponent} from "../../../cards/volume-card/volume-card.compon
import {SettingsTabId} from "../../../sidenav/preference-nav/preference-nav.component";
import {FilterField} from "../../../_models/metadata/v2/filter-field";
import {AgeRating} from "../../../_models/metadata/age-rating";
import {AgeRatingPipe} from "../../../_pipes/age-rating.pipe";
import {DefaultValuePipe} from "../../../_pipes/default-value.pipe";
import {ExternalRatingComponent} from "../external-rating/external-rating.component";
import {ReadMoreComponent} from "../../../shared/read-more/read-more.component";
import {ReadTimePipe} from "../../../_pipes/read-time.pipe";
import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
import {TimeAgoPipe} from "../../../_pipes/time-ago.pipe";
import {AgeRatingImageComponent} from "../../../_single-modules/age-rating-image/age-rating-image.component";
import {CompactNumberPipe} from "../../../_pipes/compact-number.pipe";
import {IconAndTitleComponent} from "../../../shared/icon-and-title/icon-and-title.component";
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
import {BadgeExpanderComponent} from "../../../shared/badge-expander/badge-expander.component";
import {A11yClickDirective} from "../../../shared/a11y-click.directive";
import {ScrobblingService} from "../../../_services/scrobbling.service";
import {HourEstimateRange} from "../../../_models/series-detail/hour-estimate-range";
import {ReadTimeLeftPipe} from "../../../_pipes/read-time-left.pipe";
import {PublicationStatusPipe} from "../../../_pipes/publication-status.pipe";
import {MetadataDetailRowComponent} from "../metadata-detail-row/metadata-detail-row.component";
import {DownloadButtonComponent} from "../download-button/download-button.component";
@ -142,8 +119,6 @@ import {CoverUpdateEvent} from "../../../_models/events/cover-update-event";
import {RelatedSeriesPair, RelatedTabComponent} from "../../../_single-modules/related-tab/related-tab.component";
import {CollectionTagService} from "../../../_services/collection-tag.service";
import {UserCollection} from "../../../_models/collection-tag";
import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component";
import {MangaFormatPipe} from "../../../_pipes/manga-format.pipe";
import {CoverImageComponent} from "../../../_single-module/cover-image/cover-image.component";
import {DefaultModalOptions} from "../../../_models/default-modal-options";
@ -171,16 +146,14 @@ interface StoryLineItem {
styleUrls: ['./series-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SideNavCompanionBarComponent, CardActionablesComponent, ReactiveFormsModule, NgStyle,
TagBadgeComponent, ImageComponent, NgbTooltip, NgbProgressbar, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu,
imports: [CardActionablesComponent, ReactiveFormsModule, NgStyle,
NgbTooltip, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu,
NgbDropdownItem, CarouselReelComponent, ReviewCardComponent, BulkOperationsComponent,
NgbNav, NgbNavItem, NgbNavLink, NgbNavContent, VirtualScrollerModule, CardItemComponent,
EntityTitleComponent, SeriesCardComponent, ExternalSeriesCardComponent, NgbNavOutlet,
LoadingComponent, DecimalPipe, TranslocoDirective, NgTemplateOutlet, NextExpectedCardComponent,
NgClass, NgOptimizedImage, ProviderImagePipe, AsyncPipe, PersonBadgeComponent, DetailsTabComponent, ChapterCardComponent,
VolumeCardComponent, JsonPipe, AgeRatingPipe, DefaultValuePipe, ExternalRatingComponent, ReadMoreComponent, ReadTimePipe,
RouterLink, TimeAgoPipe, AgeRatingImageComponent, CompactNumberPipe, IconAndTitleComponent, SafeHtmlPipe, BadgeExpanderComponent,
A11yClickDirective, ReadTimeLeftPipe, PublicationStatusPipe, MetadataDetailRowComponent, DownloadButtonComponent, RelatedTabComponent, SeriesFormatComponent, MangaFormatPipe, CoverImageComponent]
NgbNav, NgbNavItem, NgbNavLink, NgbNavContent, VirtualScrollerModule, SeriesCardComponent, ExternalSeriesCardComponent, NgbNavOutlet,
TranslocoDirective, NgTemplateOutlet, NextExpectedCardComponent,
NgClass, AsyncPipe, DetailsTabComponent, ChapterCardComponent,
VolumeCardComponent, DefaultValuePipe, ExternalRatingComponent, ReadMoreComponent, RouterLink, BadgeExpanderComponent,
PublicationStatusPipe, MetadataDetailRowComponent, DownloadButtonComponent, RelatedTabComponent, CoverImageComponent]
})
export class SeriesDetailComponent implements OnInit, AfterContentChecked {
@ -544,19 +517,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
});
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
onNavChange(event: NgbNavChangeEvent) {
this.bulkSelectionService.deselectAll();

View File

@ -108,22 +108,6 @@ export class CustomizeSidenavStreamsComponent implements OnDestroy {
});
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
this.cdRef.markForCheck();
}
}
constructor() {
this.pageOperationsForm.get('accessibilityMode')?.valueChanges.pipe(

View File

@ -14,36 +14,40 @@
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid ps-0" #scrollingBlock>
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
<app-card-detail-layout *ngIf="filter"
[isLoading]="isLoading"
[items]="series"
[pagination]="pagination"
[filterSettings]="filterSettings"
[filterOpen]="filterOpen"
[jumpBarKeys]="jumpbarKeys"
[trackByIdentity]="trackByIdentity"
[refresh]="refresh"
(applyFilter)="updateFilter($event)">
<ng-template #cardItem let-item let-position="idx">
<app-series-card [series]="item" [libraryId]="item.libraryId" (reload)="removeSeries($event)"
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)"
[selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
></app-series-card>
</ng-template>
@if (filter) {
<app-card-detail-layout [isLoading]="isLoading"
[items]="series"
[pagination]="pagination"
[filterSettings]="filterSettings"
[filterOpen]="filterOpen"
[jumpBarKeys]="jumpbarKeys"
[trackByIdentity]="trackByIdentity"
[refresh]="refresh"
(applyFilter)="updateFilter($event)">
<ng-template #cardItem let-item let-position="idx">
<app-series-card [series]="item" [libraryId]="item.libraryId" (reload)="removeSeries($event)"
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)"
[selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
></app-series-card>
</ng-template>
<div *ngIf="!filterActive && series.length === 0">
<ng-template #noData>
{{t('no-items')}}
</ng-template>
@if (!filterActive && series.length === 0) {
<div>
<ng-template #noData>
{{t('no-items')}}
</ng-template>
</div>
<div *ngIf="filterActive && series.length === 0">
<ng-template #noData>
{{t('no-items-filtered')}}
</ng-template>
} @else if (filterActive && series.length === 0) {
<div>
<ng-template #noData>
{{t('no-items-filtered')}}
</ng-template>
</div>
}
</app-card-detail-layout>
}
</div>
</ng-container>
</div>

View File

@ -46,7 +46,7 @@ import {SeriesFilterV2} from "../../../_models/metadata/v2/series-filter-v2";
styleUrls: ['./want-to-read.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SideNavCompanionBarComponent, NgStyle, BulkOperationsComponent, CardDetailLayoutComponent, SeriesCardComponent, NgIf, DecimalPipe, TranslocoDirective]
imports: [SideNavCompanionBarComponent, NgStyle, BulkOperationsComponent, CardDetailLayoutComponent, SeriesCardComponent, DecimalPipe, TranslocoDirective]
})
export class WantToReadComponent implements OnInit, AfterContentChecked {
@ -145,20 +145,6 @@ export class WantToReadComponent implements OnInit, AfterContentChecked {
this.scrollService.setScrollContainer(this.scrollingBlock);
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
removeSeries(seriesId: number) {
this.series = this.series.filter(s => s.id != seriesId);
this.pagination.totalItems--;