New UX Part 1.5 (#3105)

This commit is contained in:
Joe Milazzo 2024-08-11 06:10:46 -05:00 committed by GitHub
parent c188e0f23b
commit ac21b04fa4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
239 changed files with 1626 additions and 776 deletions

View File

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers;
public class ChapterController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
public ChapterController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
[HttpGet]
public async Task<ActionResult<ChapterDto>> GetChapter(int chapterId)
{
var chapter =
await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId,
ChapterIncludes.People | ChapterIncludes.Files);
return Ok(chapter);
}
}

View File

@ -229,22 +229,25 @@ public class SeriesController : BaseApiController
{
// Trigger a refresh when we are moving from a locked image to a non-locked
needsRefreshMetadata = true;
series.CoverImage = string.Empty;
series.CoverImage = null;
series.CoverImageLocked = updateSeries.CoverImageLocked;
series.ResetColorScape();
}
_unitOfWork.SeriesRepository.Update(series);
if (await _unitOfWork.CommitAsync())
if (!await _unitOfWork.CommitAsync())
{
if (needsRefreshMetadata)
{
_taskScheduler.RefreshSeriesMetadata(series.LibraryId, series.Id);
}
return Ok();
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-series-update"));
}
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-series-update"));
if (needsRefreshMetadata)
{
_taskScheduler.RefreshSeriesMetadata(series.LibraryId, series.Id);
}
return Ok();
}
/// <summary>

View File

@ -377,6 +377,7 @@ public class UploadController : BaseApiController
if (string.IsNullOrEmpty(uploadFileDto.Url))
{
library.CoverImage = null;
library.ResetColorScape();
_unitOfWork.LibraryRepository.Update(library);
if (_unitOfWork.HasChanges())
{

View File

@ -163,4 +163,10 @@ public class ChapterDto : IHasReadTimeEstimate, IHasCoverImage
public string CoverImage { get; set; }
public string PrimaryColor { get; set; }
public string SecondaryColor { get; set; }
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -19,8 +19,8 @@ public class AppUserCollectionDto : IHasCoverImage
/// </summary>
public string? CoverImage { get; set; } = string.Empty;
public string PrimaryColor { get; set; }
public string SecondaryColor { get; set; }
public string PrimaryColor { get; set; } = string.Empty;
public string SecondaryColor { get; set; } = string.Empty;
public bool CoverImageLocked { get; set; }
/// <summary>
@ -48,4 +48,10 @@ public class AppUserCollectionDto : IHasCoverImage
/// A <br/> separated string of all missing series
/// </summary>
public string? MissingSeriesFromSource { get; set; }
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -19,8 +19,8 @@ public class ReadingListDto : IHasCoverImage
/// </summary>
public string? CoverImage { get; set; } = string.Empty;
public string PrimaryColor { get; set; }
public string SecondaryColor { get; set; }
public string PrimaryColor { get; set; } = string.Empty;
public string SecondaryColor { get; set; } = string.Empty;
/// <summary>
/// Minimum Year the Reading List starts
@ -39,4 +39,10 @@ public class ReadingListDto : IHasCoverImage
/// </summary>
public int EndingMonth { get; set; }
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -66,4 +66,10 @@ public class SeriesDto : IHasReadTimeEstimate, IHasCoverImage
public string? CoverImage { get; set; }
public string PrimaryColor { get; set; }
public string SecondaryColor { get; set; }
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -66,4 +66,10 @@ public class VolumeDto : IHasReadTimeEstimate, IHasCoverImage
public string CoverImage { get; set; }
public string PrimaryColor { get; set; }
public string SecondaryColor { get; set; }
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -59,6 +59,12 @@ public class AppUserCollection : IEntityDate, IHasCoverImage
/// </summary>
public string? MissingSeriesFromSource { get; set; }
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
// Relationship
public AppUser AppUser { get; set; } = null!;
public int AppUserId { get; set; }

View File

@ -193,4 +193,10 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
{
return MinNumber.Is(Parser.DefaultChapterNumber) && !IsSpecial;
}
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -1,5 +1,7 @@
namespace API.Entities.Interfaces;
#nullable enable
public interface IHasCoverImage
{
/// <summary>
@ -16,4 +18,9 @@ public interface IHasCoverImage
/// Secondary color derived from the Cover Image
/// </summary>
public string? SecondaryColor { get; set; }
/// <summary>
/// Nulls out the ColorScape properties
/// </summary>
void ResetColorScape();
}

View File

@ -80,4 +80,10 @@ public class Library : IEntityDate, IHasCoverImage
LastScanned = (DateTime) time;
}
}
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -59,4 +59,10 @@ public class ReadingList : IEntityDate, IHasCoverImage
// Relationships
public int AppUserId { get; set; }
public AppUser AppUser { get; set; } = null!;
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -145,4 +145,10 @@ public class Series : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
NormalizedName == localizedNameNormalized ||
NormalizedLocalizedName == localizedNameNormalized;
}
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

View File

@ -73,4 +73,10 @@ public class Volume : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
return $"{MinNumber}-{MaxNumber}";
}
public void ResetColorScape()
{
PrimaryColor = string.Empty;
SecondaryColor = string.Empty;
}
}

168
UI/Web/package-lock.json generated
View File

@ -21,13 +21,13 @@
"@fortawesome/fontawesome-free": "^6.5.2",
"@iharbeck/ngx-virtual-scroller": "^17.0.2",
"@iplab/ngx-file-upload": "^17.1.0",
"@jsverse/transloco": "^7.4.3",
"@jsverse/transloco-locale": "^7.0.1",
"@jsverse/transloco-persist-lang": "^7.0.1",
"@jsverse/transloco-persist-translations": "^7.0.1",
"@jsverse/transloco-preload-langs": "^7.0.1",
"@microsoft/signalr": "^7.0.14",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@ngneat/transloco": "^6.0.4",
"@ngneat/transloco-locale": "^5.1.2",
"@ngneat/transloco-persist-lang": "^5.0.0",
"@ngneat/transloco-persist-translations": "^5.0.0",
"@ngneat/transloco-preload-langs": "^5.0.1",
"@popperjs/core": "^2.11.7",
"@swimlane/ngx-charts": "^20.5.0",
"@tweenjs/tween.js": "^23.1.1",
@ -3258,6 +3258,85 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@jsverse/transloco": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@jsverse/transloco/-/transloco-7.4.3.tgz",
"integrity": "sha512-QVzpbsfMN4oB01OfiGBz0f9/cw6nczF2EHIlhJG0455bMjiaR/tQTVGFmAGnm267iQFPtOL36yQyaHznXxPaqw==",
"dependencies": {
"@jsverse/transloco-utils": "^7.0.0",
"flat": "6.0.1",
"fs-extra": "^11.0.0",
"glob": "^10.0.0",
"lodash.kebabcase": "^4.1.1",
"ora": "^5.4.1",
"replace-in-file": "^7.0.1",
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0"
}
},
"node_modules/@jsverse/transloco-locale": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@jsverse/transloco-locale/-/transloco-locale-7.0.1.tgz",
"integrity": "sha512-mx43h2FKMKxx+Er18qArBJMxmGGW2+EShkH+xueAp+VC/ivBNQDyXWpg8hOsfNFqFQAjzlCAie1mXpbGmbM0uw==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0",
"@jsverse/transloco": ">=7.0.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@jsverse/transloco-persist-lang": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@jsverse/transloco-persist-lang/-/transloco-persist-lang-7.0.1.tgz",
"integrity": "sha512-bCH5aECb6d/NbS3/oiTqgbWrMIzo1kJzJTOVF2uazZeMa8M4xcx1Am+cX/Fo4gxAFrrTLmI4yC2dde8JB/qdCA==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0",
"@jsverse/transloco": ">=7.0.0"
}
},
"node_modules/@jsverse/transloco-persist-translations": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@jsverse/transloco-persist-translations/-/transloco-persist-translations-7.0.1.tgz",
"integrity": "sha512-BUGpcD4MrIBUbo7/G06yGdkWuVTKXVESyAJp107yUbE34Ami0+4BEK7vfLTl09ARwhBQsNKIzZgTAIpzrlK98A==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0",
"@jsverse/transloco": ">=7.0.0"
}
},
"node_modules/@jsverse/transloco-preload-langs": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@jsverse/transloco-preload-langs/-/transloco-preload-langs-7.0.1.tgz",
"integrity": "sha512-J9G+r9g8UnLWsEdf0XTUhSIX/CFoKEPP6bEfyXQ7f36FFVu3raPRoEXnqE8gQGCPiyFPG0J8YSf7lyJtUHIgHA==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0",
"@jsverse/transloco": ">=7.0.0"
}
},
"node_modules/@jsverse/transloco-utils": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@jsverse/transloco-utils/-/transloco-utils-7.0.2.tgz",
"integrity": "sha512-zud1M68mMC/Pu6irEba+Z2SzmwmmPzUPnBzMKlcGdIhzUe1z41cqQutK1M0QaQpY4h4yhumXcNaY/Ot6piv6QQ==",
"dependencies": {
"cosmiconfig": "^8.1.3",
"tslib": "^2.3.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
@ -3304,85 +3383,6 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@ngneat/transloco": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@ngneat/transloco/-/transloco-6.0.4.tgz",
"integrity": "sha512-hQSPdmzuxJIu2SBwvoiwjoUjxSnUGFyCOkJnV8IwzzmBSdgQxqMMci5WXg/bQeCYggA+RyXpUjjTudEvkWy5Rw==",
"dependencies": {
"@ngneat/transloco-utils": "^5.0.0",
"flat": "6.0.1",
"fs-extra": "^11.0.0",
"glob": "^10.0.0",
"lodash.kebabcase": "^4.1.1",
"ora": "^5.4.1",
"replace-in-file": "^7.0.1",
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0"
}
},
"node_modules/@ngneat/transloco-locale": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@ngneat/transloco-locale/-/transloco-locale-5.1.2.tgz",
"integrity": "sha512-lIEW9rjpxamXyk39kGSykR6rEbVF/Fifvp62L/8eb18X9R0quPR4YnCCkAdioZvTX2EG2tgcNWvOD2fxdgxvlQ==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=13.0.0",
"@ngneat/transloco": ">=4.0.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@ngneat/transloco-persist-lang": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@ngneat/transloco-persist-lang/-/transloco-persist-lang-5.0.0.tgz",
"integrity": "sha512-vBpHQqTeKZT+V+uvIIEv+KyCq+8HFkCa7lnjvWwcgGupSYjTvZp4PxUm+KOLLmaTIzJDL1OQEaszQ84EzX6Mzg==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0",
"@ngneat/transloco": ">=5.0.0"
}
},
"node_modules/@ngneat/transloco-persist-translations": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@ngneat/transloco-persist-translations/-/transloco-persist-translations-5.0.0.tgz",
"integrity": "sha512-QLM9X9aDRPLZhNK8f8h/4eqjhSJvHoGHRSQ+CoS3qkOXteEdOQXeYzWPHSmvDHc5lN3zNRy6sjHrBQEiZQLCKw==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0",
"@ngneat/transloco": ">=5.0.0"
}
},
"node_modules/@ngneat/transloco-preload-langs": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@ngneat/transloco-preload-langs/-/transloco-preload-langs-5.0.1.tgz",
"integrity": "sha512-+HDsEtBCFTD8YY31VX9N0dPcVp/CozxmcHXTvqjJ3M0BEkkygZIoiTQwaOPiJziNjFKl8FRhAvovWVV/t8hd8g==",
"dependencies": {
"tslib": "^2.2.0"
},
"peerDependencies": {
"@angular/core": ">=16.0.0",
"@ngneat/transloco": ">=5.0.0"
}
},
"node_modules/@ngneat/transloco-utils": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@ngneat/transloco-utils/-/transloco-utils-5.0.0.tgz",
"integrity": "sha512-e0S+GWyBTmLix9KfYWW/rScYdqQz3z3znNSb+foaA5T3jWs4CPLVo+PV0No7kGjqom8Wy8H3lLvztfhHxYSLyA==",
"dependencies": {
"cosmiconfig": "^8.1.3",
"tslib": "^2.3.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@ngtools/webpack": {
"version": "17.3.4",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.4.tgz",

View File

@ -28,13 +28,13 @@
"@fortawesome/fontawesome-free": "^6.5.2",
"@iharbeck/ngx-virtual-scroller": "^17.0.2",
"@iplab/ngx-file-upload": "^17.1.0",
"@jsverse/transloco": "^7.4.3",
"@jsverse/transloco-locale": "^7.0.1",
"@jsverse/transloco-persist-lang": "^7.0.1",
"@jsverse/transloco-persist-translations": "^7.0.1",
"@jsverse/transloco-preload-langs": "^7.0.1",
"@microsoft/signalr": "^7.0.14",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@ngneat/transloco": "^6.0.4",
"@ngneat/transloco-locale": "^5.1.2",
"@ngneat/transloco-persist-lang": "^5.0.0",
"@ngneat/transloco-persist-translations": "^5.0.0",
"@ngneat/transloco-preload-langs": "^5.0.1",
"@popperjs/core": "^2.11.7",
"@swimlane/ngx-charts": "^20.5.0",
"@tweenjs/tween.js": "^23.1.1",

View File

@ -4,7 +4,7 @@ import { ToastrService } from 'ngx-toastr';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { AccountService } from '../_services/account.service';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Injectable({
providedIn: 'root'

View File

@ -4,7 +4,7 @@ import { ToastrService } from 'ngx-toastr';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { AccountService } from '../_services/account.service';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Injectable({
providedIn: 'root'

View File

@ -10,7 +10,7 @@ import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { catchError } from 'rxjs/operators';
import { AccountService } from '../_services/account.service';
import {translate, TranslocoService} from "@ngneat/transloco";
import {translate, TranslocoService} from "@jsverse/transloco";
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {

View File

@ -80,6 +80,6 @@ export interface Chapter {
teams: Array<Person>;
locations: Array<Person>;
primaryColor?: string;
secondaryColor?: string;
primaryColor: string;
secondaryColor: string;
}

View File

@ -2,7 +2,7 @@ import {inject, Pipe, PipeTransform} from '@angular/core';
import { Observable, of } from 'rxjs';
import { AgeRating } from '../_models/metadata/age-rating';
import { AgeRatingDto } from '../_models/metadata/age-rating-dto';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'ageRating',

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {BookPageLayoutMode} from "../_models/readers/book-page-layout-mode";
@Pipe({

View File

@ -1,7 +1,7 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { CblBookResult } from 'src/app/_models/reading-list/cbl/cbl-book-result';
import { CblImportReason } from 'src/app/_models/reading-list/cbl/cbl-import-reason.enum';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
const failIcon = '<i aria-hidden="true" class="reading-list-fail--item fa-solid fa-circle-xmark me-1"></i>';
const successIcon = '<i aria-hidden="true" class="reading-list-success--item fa-solid fa-circle-check me-1"></i>';

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { CblImportResult } from 'src/app/_models/reading-list/cbl/cbl-import-result.enum';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'cblImportResult',

View File

@ -1,6 +1,6 @@
import {Pipe, PipeTransform} from '@angular/core';
import {CoverImageSize} from "../admin/_models/cover-image-size";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'coverImageSize',

View File

@ -1,6 +1,6 @@
import {Pipe, PipeTransform} from '@angular/core';
import { DayOfWeek } from 'src/app/_services/statistics.service';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'dayOfWeek',

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'defaultDate',

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { DevicePlatform } from 'src/app/_models/device/device-platform';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'devicePlatform',

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {FileTypeGroup} from "../_models/library/file-type-group.enum";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'fileTypeGroup',

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import { FilterComparison } from 'src/app/_models/metadata/v2/filter-comparison';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'filterComparison',

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import { FilterField } from 'src/app/_models/metadata/v2/filter-field';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'filterField',

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {LayoutMode} from "../manga-reader/_models/layout-mode";
@Pipe({

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { LibraryType } from '../_models/library/library';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
/**
* Returns the name of the LibraryType

View File

@ -1,6 +1,6 @@
import {Pipe, PipeTransform} from '@angular/core';
import { MangaFormat } from '../_models/manga-format';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
/**
* Returns the string name for the format

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {PageLayoutMode} from "../_models/page-layout-mode";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'pageLayoutMode',

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {PageSplitOption} from "../_models/preferences/page-split-option";
@Pipe({

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {PdfScrollMode} from "../_models/preferences/pdf-scroll-mode";
@Pipe({

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {PdfSpreadMode} from "../_models/preferences/pdf-spread-mode";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'pdfSpreadMode',

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {PdfTheme} from "../_models/preferences/pdf-theme";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'pdfTheme',

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { PersonRole } from '../_models/metadata/person';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'personRole',

View File

@ -1,6 +1,6 @@
import {Pipe, PipeTransform} from '@angular/core';
import { PublicationStatus } from '../_models/metadata/publication-status';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'publicationStatus',

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {ReadingDirection} from "../_models/preferences/reading-direction";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'readingDirection',
@ -11,7 +11,7 @@ export class ReadingDirectionPipe implements PipeTransform {
transform(value: ReadingDirection): string {
switch (value) {
case ReadingDirection.LeftToRight: return translate('preferences.left-to-right');
case ReadingDirection.RightToLeft: return translate('preferences.right-to-right');
case ReadingDirection.RightToLeft: return translate('preferences.right-to-left');
}
}

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {ReaderMode} from "../_models/preferences/reader-mode";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'readerMode',
@ -10,7 +10,7 @@ export class ReaderModePipe implements PipeTransform {
transform(value: ReaderMode): string {
switch (value) {
case ReaderMode.UpDown: return translate('preferences.up-down');
case ReaderMode.UpDown: return translate('preferences.up-to-down');
case ReaderMode.Webtoon: return translate('preferences.webtoon');
case ReaderMode.LeftRight: return translate('preferences.left-to-right');
}

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { RelationKind } from '../_models/series-detail/relation-kind';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'relationship',

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {ScalingOption} from "../_models/preferences/scaling-option";
@Pipe({

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import {ScrobbleEventType} from "../_models/scrobbling/scrobble-event";
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'scrobbleEventType',

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {SettingsTabId} from "../sidenav/preference-nav/preference-nav.component";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
/**
* Translates the fragment for Settings to a User title

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { ThemeProvider } from 'src/app/_models/preferences/site-theme';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({

View File

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core';
import {SortField} from "../_models/metadata/series-filter";
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
@Pipe({
name: 'sortField',

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
@Pipe({
name: 'streamName',

View File

@ -1,5 +1,5 @@
import {ChangeDetectorRef, NgZone, OnDestroy, Pipe, PipeTransform} from '@angular/core';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
/**
* MIT License

View File

@ -1,5 +1,5 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
/**
* Converts hours -> days, months, years, etc

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {WritingStyle} from "../_models/preferences/writing-style";
@Pipe({

View File

@ -11,6 +11,7 @@ import { AccountService } from './account.service';
import { DeviceService } from './device.service';
import {SideNavStream} from "../_models/sidenav/sidenav-stream";
import {SmartFilter} from "../_models/metadata/v2/smart-filter";
import {translate} from "@jsverse/transloco";
export enum Action {
Submenu = -1,
@ -117,6 +118,7 @@ export type ActionAllowedCallback<T> = (action: ActionItem<T>) => boolean;
export interface ActionItem<T> {
title: string;
description: string;
action: Action;
callback: ActionCallback<T>;
requiresAdmin: boolean;
@ -208,15 +210,6 @@ export class ActionFactoryService {
return this.applyCallbackToList(this.bookmarkActions, callback);
}
getMetadataFilterActions(callback: ActionCallback<any>) {
const actions = [
{title: 'add-rule-group-and', action: Action.AddRuleGroup, requiresAdmin: false, children: [], callback: this.dummyCallback},
{title: 'add-rule-group-or', action: Action.AddRuleGroup, requiresAdmin: false, children: [], callback: this.dummyCallback},
{title: 'remove-rule-group', action: Action.RemoveRuleGroup, requiresAdmin: false, children: [], callback: this.dummyCallback},
];
return this.applyCallbackToList(actions, callback);
}
dummyCallback(action: ActionItem<any>, data: any) {}
filterSendToAction(actions: Array<ActionItem<Chapter>>, chapter: Chapter) {
@ -227,11 +220,44 @@ export class ActionFactoryService {
return actions;
}
getActionablesForSettingsPage(actions: Array<ActionItem<any>>, blacklist: Array<Action> = []) {
const tasks = [];
let actionItem;
for (let parent of actions) {
if (parent.action === Action.SendTo) continue;
if (parent.children.length === 0) {
actionItem = {...parent};
actionItem.title = translate('actionable.' + actionItem.title);
if (actionItem.description !== '') {
actionItem.description = translate('actionable.' + actionItem.description);
}
tasks.push(actionItem);
continue;
}
for (let child of parent.children) {
actionItem = {...child};
actionItem.title = translate('actionable.' + actionItem.title);
if (actionItem.description !== '') {
actionItem.description = translate('actionable.' + actionItem.description);
}
tasks.push(actionItem);
}
}
// Filter out tasks that don't make sense
return tasks.filter(t => !blacklist.includes(t.action));
}
private _resetActions() {
this.libraryActions = [
{
action: Action.Scan,
title: 'scan-library',
description: 'scan-library-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -239,12 +265,14 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'others',
description: '',
callback: this.dummyCallback,
requiresAdmin: true,
children: [
{
action: Action.RefreshMetadata,
title: 'refresh-covers',
description: 'refresh-covers-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -252,6 +280,7 @@ export class ActionFactoryService {
{
action: Action.GenerateColorScape,
title: 'generate-colorscape',
description: 'generate-colorscape-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -259,6 +288,7 @@ export class ActionFactoryService {
{
action: Action.AnalyzeFiles,
title: 'analyze-files',
description: 'analyze-files-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -266,6 +296,7 @@ export class ActionFactoryService {
{
action: Action.Delete,
title: 'delete',
description: 'delete-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -275,6 +306,7 @@ export class ActionFactoryService {
{
action: Action.Edit,
title: 'settings',
description: 'settings-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -285,6 +317,7 @@ export class ActionFactoryService {
{
action: Action.Edit,
title: 'edit',
description: 'edit-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -292,6 +325,7 @@ export class ActionFactoryService {
{
action: Action.Delete,
title: 'delete',
description: 'delete-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
class: 'danger',
@ -300,6 +334,7 @@ export class ActionFactoryService {
{
action: Action.Promote,
title: 'promote',
description: 'promote-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -307,6 +342,7 @@ export class ActionFactoryService {
{
action: Action.UnPromote,
title: 'unpromote',
description: 'unpromote-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -317,6 +353,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsRead,
title: 'mark-as-read',
description: 'mark-as-read-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -324,6 +361,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsUnread,
title: 'mark-as-unread',
description: 'mark-as-unread-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -331,6 +369,7 @@ export class ActionFactoryService {
{
action: Action.Scan,
title: 'scan-series',
description: 'scan-series-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -338,12 +377,14 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'add-to',
description: '',
callback: this.dummyCallback,
requiresAdmin: false,
children: [
{
action: Action.AddToWantToReadList,
title: 'add-to-want-to-read',
description: 'add-to-want-to-read-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -351,6 +392,7 @@ export class ActionFactoryService {
{
action: Action.RemoveFromWantToReadList,
title: 'remove-from-want-to-read',
description: 'remove-to-want-to-read-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -358,6 +400,7 @@ export class ActionFactoryService {
{
action: Action.AddToReadingList,
title: 'add-to-reading-list',
description: 'add-to-reading-list-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -365,6 +408,7 @@ export class ActionFactoryService {
{
action: Action.AddToCollection,
title: 'add-to-collection',
description: 'add-to-collection-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -374,12 +418,14 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'send-to',
description: 'send-to-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [
{
action: Action.SendTo,
title: '',
description: '',
callback: this.dummyCallback,
requiresAdmin: false,
dynamicList: this.deviceService.devices$.pipe(map((devices: Array<Device>) => devices.map(d => {
@ -392,12 +438,22 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'others',
description: '',
callback: this.dummyCallback,
requiresAdmin: true,
children: [
{
action: Action.RefreshMetadata,
title: 'refresh-covers',
description: 'refresh-covers-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
},
{
action: Action.GenerateColorScape,
title: 'generate-colorscape',
description: 'generate-colorscape-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -405,6 +461,7 @@ export class ActionFactoryService {
{
action: Action.AnalyzeFiles,
title: 'analyze-files',
description: 'analyze-files-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -412,6 +469,7 @@ export class ActionFactoryService {
{
action: Action.Delete,
title: 'delete',
description: 'delete-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
class: 'danger',
@ -422,6 +480,7 @@ export class ActionFactoryService {
{
action: Action.Download,
title: 'download',
description: 'download-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -429,6 +488,7 @@ export class ActionFactoryService {
{
action: Action.Edit,
title: 'edit',
description: 'edit-tooltip',
callback: this.dummyCallback,
requiresAdmin: true,
children: [],
@ -439,6 +499,7 @@ export class ActionFactoryService {
{
action: Action.IncognitoRead,
title: 'read-incognito',
description: 'read-incognito-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -446,6 +507,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsRead,
title: 'mark-as-read',
description: 'mark-as-read-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -453,6 +515,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsUnread,
title: 'mark-as-unread',
description: 'mark-as-unread-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -460,12 +523,14 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'add-to',
description: '=',
callback: this.dummyCallback,
requiresAdmin: false,
children: [
{
action: Action.AddToReadingList,
title: 'add-to-reading-list',
description: 'add-to-reading-list-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -475,12 +540,14 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'send-to',
description: 'send-to-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [
{
action: Action.SendTo,
title: '',
description: '',
callback: this.dummyCallback,
requiresAdmin: false,
dynamicList: this.deviceService.devices$.pipe(map((devices: Array<Device>) => devices.map(d => {
@ -493,6 +560,7 @@ export class ActionFactoryService {
{
action: Action.Download,
title: 'download',
description: 'download-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -500,6 +568,7 @@ export class ActionFactoryService {
{
action: Action.Edit,
title: 'details',
description: 'edit-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -510,6 +579,7 @@ export class ActionFactoryService {
{
action: Action.IncognitoRead,
title: 'read-incognito',
description: 'read-incognito-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -517,6 +587,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsRead,
title: 'mark-as-read',
description: 'mark-as-read-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -524,6 +595,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsUnread,
title: 'mark-as-unread',
description: 'mark-as-unread-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -531,12 +603,14 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'add-to',
description: '',
callback: this.dummyCallback,
requiresAdmin: false,
children: [
{
action: Action.AddToReadingList,
title: 'add-to-reading-list',
description: 'add-to-reading-list-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -546,12 +620,14 @@ export class ActionFactoryService {
{
action: Action.Submenu,
title: 'send-to',
description: 'send-to-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [
{
action: Action.SendTo,
title: '',
description: '',
callback: this.dummyCallback,
requiresAdmin: false,
dynamicList: this.deviceService.devices$.pipe(map((devices: Array<Device>) => devices.map(d => {
@ -565,6 +641,7 @@ export class ActionFactoryService {
{
action: Action.Download,
title: 'download',
description: 'download-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -572,6 +649,7 @@ export class ActionFactoryService {
{
action: Action.Edit,
title: 'details',
description: 'edit-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -582,6 +660,7 @@ export class ActionFactoryService {
{
action: Action.Edit,
title: 'edit',
description: 'edit-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -589,6 +668,7 @@ export class ActionFactoryService {
{
action: Action.Delete,
title: 'delete',
description: 'delete-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
class: 'danger',
@ -597,6 +677,7 @@ export class ActionFactoryService {
{
action: Action.Promote,
title: 'promote',
description: 'promote-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -604,6 +685,7 @@ export class ActionFactoryService {
{
action: Action.UnPromote,
title: 'unpromote',
description: 'unpromote-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -614,6 +696,7 @@ export class ActionFactoryService {
{
action: Action.ViewSeries,
title: 'view-series',
description: 'view-series-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -621,6 +704,7 @@ export class ActionFactoryService {
{
action: Action.DownloadBookmark,
title: 'download',
description: 'download-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -628,6 +712,7 @@ export class ActionFactoryService {
{
action: Action.Delete,
title: 'clear',
description: 'delete-tooltip',
callback: this.dummyCallback,
class: 'danger',
requiresAdmin: false,
@ -639,6 +724,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsVisible,
title: 'mark-visible',
description: 'mark-visible-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -646,6 +732,7 @@ export class ActionFactoryService {
{
action: Action.MarkAsInvisible,
title: 'mark-invisible',
description: 'mark-invisible-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],
@ -656,6 +743,7 @@ export class ActionFactoryService {
{
action: Action.Delete,
title: 'delete',
description: 'delete-tooltip',
callback: this.dummyCallback,
requiresAdmin: false,
children: [],

View File

@ -19,7 +19,7 @@ import { LibraryService } from './library.service';
import { MemberService } from './member.service';
import { ReaderService } from './reader.service';
import { SeriesService } from './series.service';
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {UserCollection} from "../_models/collection-tag";
import {CollectionTagService} from "./collection-tag.service";
import {SmartFilter} from "../_models/metadata/v2/smart-filter";
@ -87,7 +87,7 @@ export class ActionService implements OnDestroy {
* @param forceUpdate Optional Should we force
* @returns
*/
async refreshMetadata(library: Partial<Library>, callback?: LibraryActionCallback, forceUpdate: boolean = true) {
async refreshLibraryMetadata(library: Partial<Library>, callback?: LibraryActionCallback, forceUpdate: boolean = true) {
if (!library.hasOwnProperty('id') || library.id === undefined) {
return;
}
@ -102,8 +102,11 @@ export class ActionService implements OnDestroy {
}
}
const message = forceUpdate ? 'toasts.refresh-covers-queued' : 'toasts.generate-colorscape-queued';
this.libraryService.refreshMetadata(library?.id, forceUpdate).subscribe((res: any) => {
this.toastr.info(translate('toasts.scan-queued', {name: library.name}));
this.toastr.info(translate(message, {name: library.name}));
if (callback) {
callback(library);
}
@ -226,17 +229,24 @@ export class ActionService implements OnDestroy {
* Start a metadata refresh for a Series
* @param series Series, must have libraryId, id and name populated
* @param callback Optional callback to perform actions after API completes
* @param forceUpdate If cache should be checked or not
*/
async refreshMetdata(series: Series, callback?: SeriesActionCallback) {
if (!await this.confirmService.confirm(translate('toasts.confirm-regen-covers'))) {
if (callback) {
callback(series);
async refreshSeriesMetadata(series: Series, callback?: SeriesActionCallback, forceUpdate: boolean = true) {
// Prompt the user if we are doing a forced call
if (forceUpdate) {
if (!await this.confirmService.confirm(translate('toasts.confirm-regen-covers'))) {
if (callback) {
callback(series);
}
return;
}
return;
}
this.seriesService.refreshMetadata(series).pipe(take(1)).subscribe((res: any) => {
this.toastr.info(translate('toasts.refresh-covers-queued', {name: series.name}));
const message = forceUpdate ? 'toasts.refresh-covers-queued' : 'toasts.generate-colorscape-queued';
this.seriesService.refreshMetadata(series, forceUpdate).pipe(take(1)).subscribe((res: any) => {
this.toastr.info(translate(message, {name: series.name}));
if (callback) {
callback(series);
}

View File

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import {environment} from "../../environments/environment";
import {HttpClient} from "@angular/common/http";
import {AccountService} from "./account.service";
import {UserCollection} from "../_models/collection-tag";
import {Chapter} from "../_models/chapter";
import {HourEstimateRange} from "../_models/series-detail/hour-estimate-range";
@Injectable({
providedIn: 'root'
})
export class ChapterService {
baseUrl = environment.apiUrl;
constructor(private httpClient: HttpClient) { }
getChapterMetadata(chapterId: number) {
return this.httpClient.get<Chapter>(this.baseUrl + 'chapter/?chapterId=' + chapterId);
}
}

View File

@ -143,8 +143,8 @@ export class SeriesService {
}
refreshMetadata(series: Series) {
return this.httpClient.post(this.baseUrl + 'series/refresh-metadata', {libraryId: series.libraryId, seriesId: series.id});
refreshMetadata(series: Series, force = true) {
return this.httpClient.post(this.baseUrl + 'series/refresh-metadata', {libraryId: series.libraryId, seriesId: series.id, forceUpdate: force});
}
scan(libraryId: number, seriesId: number, force = false) {

View File

@ -13,7 +13,7 @@ import { StatCount } from '../statistics/_models/stat-count';
import { PublicationStatus } from '../_models/metadata/publication-status';
import { MangaFormat } from '../_models/manga-format';
import { TextResonse } from '../_types/text-response';
import {TranslocoService} from "@ngneat/transloco";
import {TranslocoService} from "@jsverse/transloco";
import {KavitaPlusMetadataBreakdown} from "../statistics/_models/kavitaplus-metadata-breakdown";
import {throttleTime} from "rxjs/operators";
import {DEBOUNCE_TIME} from "../shared/_services/download.service";

View File

@ -19,7 +19,7 @@ import {SiteTheme, ThemeProvider} from '../_models/preferences/site-theme';
import {TextResonse} from '../_types/text-response';
import {EVENTS, MessageHubService} from './message-hub.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {translate} from "@ngneat/transloco";
import {translate} from "@jsverse/transloco";
import {DownloadableSiteTheme} from "../_models/theme/downloadable-site-theme";
import {NgxFileDropEntry} from "ngx-file-drop";
import {SiteThemeUpdatedEvent} from "../_models/events/site-theme-updated-event";

View File

@ -12,7 +12,7 @@ import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle} from '
import { AccountService } from 'src/app/_services/account.service';
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
import {AsyncPipe, CommonModule, NgTemplateOutlet} from "@angular/common";
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {DynamicListPipe} from "./_pipes/dynamic-list.pipe";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";

View File

@ -13,7 +13,7 @@ import {ReactiveFormsModule} from "@angular/forms";
import {UserReview} from "../review-card/user-review";
import {SpoilerComponent} from "../spoiler/spoiler.component";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {ProviderImagePipe} from "../../_pipes/provider-image.pipe";

View File

@ -1,5 +1,5 @@
<ng-container *transloco="let t; read:'review-card'">
<div class="card clickable mb-3" style="max-width: 320px; max-height: 160px; height: 160px" (click)="showModal()">
<div class="card review-card clickable mb-3" (click)="showModal()">
<div class="row g-0">
<div class="col-md-2 d-none d-md-block">
<i class="img-fluid rounded-start fa-solid fa-circle-user profile-image" aria-hidden="true"></i>

View File

@ -1,3 +1,10 @@
.review-card {
max-width: 320px;
max-height: 160px;
height: 160px;
width: 320px;
}
.profile-image {
font-size: 1.2rem;
padding: 20px;

View File

@ -22,7 +22,7 @@ import {ReadMoreComponent} from "../../shared/read-more/read-more.component";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {ImageComponent} from "../../shared/image/image.component";
import {ProviderImagePipe} from "../../_pipes/provider-image.pipe";
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {ScrobbleProvider} from "../../_services/scrobbling.service";
@Component({

View File

@ -12,7 +12,7 @@ import {NgbActiveModal, NgbRating} from '@ng-bootstrap/ng-bootstrap';
import { SeriesService } from 'src/app/_services/series.service';
import {UserReview} from "../review-card/user-review";
import {CommonModule} from "@angular/common";
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {ConfirmService} from "../../shared/confirm.service";
import {ToastrService} from "ngx-toastr";

View File

@ -1,6 +1,6 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnInit} from '@angular/core';
import {CommonModule, NgOptimizedImage} from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {NgbActiveOffcanvas, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {ExternalSeriesDetail, SeriesStaff} from "../../_models/series-detail/external-series-detail";
import {SeriesService} from "../../_services/series.service";

View File

@ -9,7 +9,7 @@ import {
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
@Component({
selector: 'app-spoiler',

View File

@ -11,9 +11,9 @@ import {debounceTime, take} from "rxjs/operators";
import {PaginatedResult, Pagination} from "../../_models/pagination";
import {SortableHeader, SortEvent} from "../table/_directives/sortable-header.directive";
import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";
import {translate, TranslocoModule} from "@ngneat/transloco";
import {translate, TranslocoModule} from "@jsverse/transloco";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
import {TranslocoLocaleModule} from "@jsverse/transloco-locale";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {ToastrService} from "ngx-toastr";
import {LooseLeafOrDefaultNumber, SpecialVolumeNumber} from "../../_models/chapter";

View File

@ -6,7 +6,7 @@ import { DirectoryDto } from 'src/app/_models/system/directory-dto';
import { LibraryService } from '../../../_services/library.service';
import { NgIf, NgFor, NgClass } from '@angular/common';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {WikiLink} from "../../../_models/wiki";

View File

@ -5,7 +5,7 @@ import {Member} from 'src/app/_models/auth/member';
import {LibraryService} from 'src/app/_services/library.service';
import {NgFor, NgIf} from '@angular/common';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {SelectionModel} from "../../../typeahead/_models/selection-model";
@Component({

View File

@ -5,7 +5,7 @@ import { Member } from 'src/app/_models/auth/member';
import { AccountService } from 'src/app/_services/account.service';
import { SentenceCasePipe } from '../../../_pipes/sentence-case.pipe';
import { NgIf } from '@angular/common';
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {ToastrService} from "ngx-toastr";
@Component({

View File

@ -10,7 +10,7 @@ import {RestrictionSelectorComponent} from '../../user-settings/restriction-sele
import {LibrarySelectorComponent} from '../library-selector/library-selector.component';
import {RoleSelectorComponent} from '../role-selector/role-selector.component';
import {NgIf} from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
const AllowedUsernameCharacters = /^[\sa-zA-Z0-9\-._@+/\s]*$/;

View File

@ -12,7 +12,7 @@ import { RestrictionSelectorComponent } from '../../user-settings/restriction-se
import { LibrarySelectorComponent } from '../library-selector/library-selector.component';
import { RoleSelectorComponent } from '../role-selector/role-selector.component';
import { NgIf } from '@angular/common';
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
@Component({

View File

@ -12,7 +12,7 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {Library} from 'src/app/_models/library/library';
import {Member} from 'src/app/_models/auth/member';
import {LibraryService} from 'src/app/_services/library.service';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {LoadingComponent} from "../../shared/loading/loading.component";
import {SelectionModel} from "../../typeahead/_models/selection-model";

View File

@ -13,7 +13,7 @@ import { LoadingComponent } from '../../shared/loading/loading.component';
import { NgbTooltip, NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
import { NgIf } from '@angular/common';
import {environment} from "../../../environments/environment";
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {catchError} from "rxjs";
import {WikiLink} from "../../_models/wiki";
import {RouterLink} from "@angular/router";

View File

@ -89,9 +89,7 @@
<app-setting-switch [title]="t('enable-ssl-label')">
<ng-template #switch>
<div class="form-check form-switch">
<div class="form-check form-switch">
<input id="setting-enable-ssl" type="checkbox" class="form-check-input" formControlName="enableOpds">
</div>
<input id="setting-enable-ssl" type="checkbox" class="form-check-input" formControlName="enableOpds">
</div>
</ng-template>
</app-setting-switch>
@ -142,9 +140,7 @@
<app-setting-switch [title]="t('customized-templates-label')" [subtitle]="t('customized-templates-tooltip')">
<ng-template #switch>
<div class="form-check form-switch">
<div class="form-check form-switch">
<input id="settings-customized-templates" type="checkbox" class="form-check-input" formControlName="customizedTemplates">
</div>
<input id="settings-customized-templates" type="checkbox" class="form-check-input" formControlName="customizedTemplates">
</div>
</ng-template>
</app-setting-switch>
@ -154,8 +150,6 @@
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mt-4">
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="test()">{{t('test')}}</button>
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">{{t('reset-to-default')}}</button>
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()">{{t('reset')}}</button>
<button type="submit" class="flex-fill btn btn-primary" (click)="saveSettings()" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
</div>
</form>

View File

@ -1,7 +1,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {ToastrService} from 'ngx-toastr';
import {take} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, switchMap, take, tap} from 'rxjs';
import {SettingsService} from '../settings.service';
import {ServerSettings} from '../_models/server-settings';
import {
@ -9,13 +9,14 @@ import {
NgbTooltip
} from '@ng-bootstrap/ng-bootstrap';
import {NgIf, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule} from "@ngneat/transloco";
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";
@Component({
selector: 'app-manage-email-settings',
@ -31,6 +32,7 @@ export class ManageEmailSettingsComponent implements OnInit {
private readonly cdRef = inject(ChangeDetectorRef);
private readonly settingsService = inject(SettingsService);
private readonly toastr = inject(ToastrService);
private readonly destroyRef = inject(DestroyRef);
serverSettings!: ServerSettings;
settingsForm: FormGroup = new FormGroup({});
@ -50,6 +52,23 @@ export class ManageEmailSettingsComponent implements OnInit {
this.settingsForm.addControl('sizeLimit', new FormControl(this.serverSettings.smtpConfig.sizeLimit, [Validators.min(1)]));
this.settingsForm.addControl('customizedTemplates', new FormControl(this.serverSettings.smtpConfig.customizedTemplates, [Validators.min(1)]));
// Automatically save settings as we edit them
this.settingsForm.valueChanges.pipe(
distinctUntilChanged(),
debounceTime(100),
filter(_ => this.settingsForm.valid),
takeUntilDestroyed(this.destroyRef),
switchMap(_ => {
const data = this.packData();
return this.settingsService.updateServerSettings(data);
}),
tap(settings => {
this.serverSettings = settings;
this.resetForm();
this.cdRef.markForCheck();
})
).subscribe();
this.cdRef.markForCheck();
});
}
@ -57,15 +76,15 @@ export class ManageEmailSettingsComponent implements OnInit {
resetForm() {
this.settingsForm.get('hostName')?.setValue(this.serverSettings.hostName);
this.settingsForm.addControl('host', new FormControl(this.serverSettings.smtpConfig.host, []));
this.settingsForm.addControl('port', new FormControl(this.serverSettings.smtpConfig.port, []));
this.settingsForm.addControl('userName', new FormControl(this.serverSettings.smtpConfig.userName, []));
this.settingsForm.addControl('enableSsl', new FormControl(this.serverSettings.smtpConfig.enableSsl, []));
this.settingsForm.addControl('password', new FormControl(this.serverSettings.smtpConfig.password, []));
this.settingsForm.addControl('senderAddress', new FormControl(this.serverSettings.smtpConfig.senderAddress, []));
this.settingsForm.addControl('senderDisplayName', new FormControl(this.serverSettings.smtpConfig.senderDisplayName, []));
this.settingsForm.addControl('sizeLimit', new FormControl(this.serverSettings.smtpConfig.sizeLimit, [Validators.min(1)]));
this.settingsForm.addControl('customizedTemplates', new FormControl(this.serverSettings.smtpConfig.customizedTemplates, [Validators.min(1)]));
this.settingsForm.get('host')?.setValue(this.serverSettings.smtpConfig.host, {onlySelf: true, emitEvent: false});
this.settingsForm.get('port')?.setValue(this.serverSettings.smtpConfig.port, {onlySelf: true, emitEvent: false});
this.settingsForm.get('userName')?.setValue(this.serverSettings.smtpConfig.userName, {onlySelf: true, emitEvent: false});
this.settingsForm.get('enableSsl')?.setValue(this.serverSettings.smtpConfig.enableSsl, {onlySelf: true, emitEvent: false});
this.settingsForm.get('password')?.setValue(this.serverSettings.smtpConfig.password, {onlySelf: true, emitEvent: false});
this.settingsForm.get('senderAddress')?.setValue(this.serverSettings.smtpConfig.senderAddress, {onlySelf: true, emitEvent: false});
this.settingsForm.get('senderDisplayName')?.setValue(this.serverSettings.smtpConfig.senderDisplayName, {onlySelf: true, emitEvent: false});
this.settingsForm.get('sizeLimit')?.setValue(this.serverSettings.smtpConfig.sizeLimit, {onlySelf: true, emitEvent: false});
this.settingsForm.get('customizedTemplates')?.setValue(this.serverSettings.smtpConfig.customizedTemplates, {onlySelf: true, emitEvent: false});
this.settingsForm.markAsPristine();
this.cdRef.markForCheck();
}
@ -88,7 +107,7 @@ export class ManageEmailSettingsComponent implements OnInit {
this.cdRef.markForCheck();
}
async saveSettings() {
packData() {
const modelSettings = Object.assign({}, this.serverSettings);
modelSettings.emailServiceUrl = this.settingsForm.get('emailServiceUrl')?.value;
modelSettings.hostName = this.settingsForm.get('hostName')?.value;
@ -103,6 +122,12 @@ export class ManageEmailSettingsComponent implements OnInit {
modelSettings.smtpConfig.sizeLimit = this.settingsForm.get('sizeLimit')?.value;
modelSettings.smtpConfig.customizedTemplates = this.settingsForm.get('customizedTemplates')?.value;
return modelSettings;
}
async saveSettings() {
const modelSettings = this.packData();
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => {
this.serverSettings = settings;
this.resetForm();

View File

@ -21,7 +21,7 @@ import { SentenceCasePipe } from '../../_pipes/sentence-case.pipe';
import { TimeAgoPipe } from '../../_pipes/time-ago.pipe';
import { LibraryTypePipe } from '../../_pipes/library-type.pipe';
import { RouterLink } from '@angular/router';
import {translate, TranslocoModule} from "@ngneat/transloco";
import {translate, TranslocoModule} from "@jsverse/transloco";
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
import {AsyncPipe, TitleCasePipe} from "@angular/common";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
@ -164,10 +164,10 @@ export class ManageLibraryComponent implements OnInit {
await this.actionService.scanLibrary(library);
break;
case(Action.RefreshMetadata):
await this.actionService.refreshMetadata(library);
await this.actionService.refreshLibraryMetadata(library);
break;
case(Action.GenerateColorScape):
await this.actionService.refreshMetadata(library, undefined, false);
await this.actionService.refreshLibraryMetadata(library, undefined, false);
break;
case(Action.Edit):
this.editLibrary(library)

View File

@ -19,7 +19,7 @@ import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import { FilterPipe } from '../../_pipes/filter.pipe';
import { LoadingComponent } from '../../shared/loading/loading.component';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {WikiLink} from "../../_models/wiki";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";

View File

@ -71,8 +71,6 @@
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end">
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">{{t('reset-to-default')}}</button>
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()">{{t('reset')}}</button>
<button type="submit" class="flex-fill btn btn-primary" (click)="saveSettings()" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
</div>
</form>

View File

@ -1,7 +1,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {ToastrService} from 'ngx-toastr';
import {take} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, switchMap, take, tap} from 'rxjs';
import {SettingsService} from '../settings.service';
import {ServerSettings} from '../_models/server-settings';
import {DirectoryPickerComponent, DirectoryPickerResult} from '../_modals/directory-picker/directory-picker.component';
@ -20,7 +20,7 @@ import {
import {allEncodeFormats} from '../_models/encode-format';
import {ManageMediaIssuesComponent} from '../manage-media-issues/manage-media-issues.component';
import {NgFor, NgIf, NgTemplateOutlet} from '@angular/common';
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
import {allCoverImageSizes} from '../_models/cover-image-size';
import {pageLayoutModes} from "../../_models/preferences/preferences";
import {PageLayoutModePipe} from "../../_pipes/page-layout-mode.pipe";
@ -28,6 +28,7 @@ import {SettingItemComponent} from "../../settings/_components/setting-item/sett
import {EncodeFormatPipe} from "../../_pipes/encode-format.pipe";
import {CoverImageSizePipe} from "../../_pipes/cover-image-size.pipe";
import {ConfirmService} from "../../shared/confirm.service";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-manage-media-settings',
@ -47,6 +48,7 @@ export class ManageMediaSettingsComponent implements OnInit {
private readonly settingsService = inject(SettingsService);
private readonly toastr = inject(ToastrService);
private readonly modalService = inject(NgbModal);
private readonly destroyRef = inject(DestroyRef);
protected readonly allEncodeFormats = allEncodeFormats;
protected readonly allCoverImageSizes = allCoverImageSizes;
@ -61,33 +63,46 @@ export class ManageMediaSettingsComponent implements OnInit {
this.settingsForm.addControl('encodeMediaAs', new FormControl(this.serverSettings.encodeMediaAs, [Validators.required]));
this.settingsForm.addControl('bookmarksDirectory', new FormControl(this.serverSettings.bookmarksDirectory, [Validators.required]));
this.settingsForm.addControl('coverImageSize', new FormControl(this.serverSettings.coverImageSize, [Validators.required]));
// Automatically save settings as we edit them
this.settingsForm.valueChanges.pipe(
distinctUntilChanged(),
debounceTime(100),
filter(_ => this.settingsForm.valid),
takeUntilDestroyed(this.destroyRef),
switchMap(_ => {
const data = this.packData();
return this.settingsService.updateServerSettings(data);
}),
tap(settings => {
this.serverSettings = settings;
this.resetForm();
this.cdRef.markForCheck();
})
).subscribe();
this.cdRef.markForCheck();
});
}
resetForm() {
this.settingsForm.get('encodeMediaAs')?.setValue(this.serverSettings.encodeMediaAs);
this.settingsForm.get('bookmarksDirectory')?.setValue(this.serverSettings.bookmarksDirectory);
this.settingsForm.get('coverImageSize')?.setValue(this.serverSettings.coverImageSize);
this.settingsForm.get('encodeMediaAs')?.setValue(this.serverSettings.encodeMediaAs, {onlySelf: true, emitEvent: false});
this.settingsForm.get('bookmarksDirectory')?.setValue(this.serverSettings.bookmarksDirectory, {onlySelf: true, emitEvent: false});
this.settingsForm.get('coverImageSize')?.setValue(this.serverSettings.coverImageSize, {onlySelf: true, emitEvent: false});
this.settingsForm.markAsPristine();
this.cdRef.markForCheck();
}
saveSettings() {
packData() {
const modelSettings = Object.assign({}, this.serverSettings);
modelSettings.encodeMediaAs = parseInt(this.settingsForm.get('encodeMediaAs')?.value, 10);
modelSettings.bookmarksDirectory = this.settingsForm.get('bookmarksDirectory')?.value;
modelSettings.coverImageSize = parseInt(this.settingsForm.get('coverImageSize')?.value, 10);
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe(async (settings: ServerSettings) => {
this.serverSettings = settings;
this.resetForm();
this.toastr.success(this.translocoService.translate('toasts.server-settings-updated'));
}, (err: any) => {
console.error('error: ', err);
});
return modelSettings;
}
async resetToDefaults() {
if (!await this.confirmService.confirm(translate('toasts.confirm-reset-server-settings'))) return;

View File

@ -25,10 +25,10 @@ import {EditSeriesModalComponent} from "../../cards/_modals/edit-series-modal/ed
import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
import {FilterPipe} from "../../_pipes/filter.pipe";
import {LoadingComponent} from "../../shared/loading/loading.component";
import {TranslocoModule} from "@ngneat/transloco";
import {TranslocoModule} from "@jsverse/transloco";
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
import {TranslocoLocaleModule} from "@jsverse/transloco-locale";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
@Component({

View File

@ -318,8 +318,6 @@
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mt-4">
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">{{t('reset-to-default')}}</button>
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()">{{t('reset')}}</button>
<button type="submit" class="flex-fill btn btn-primary" (click)="saveSettings()" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
</div>
</form>

View File

@ -1,4 +1,4 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {ToastrService} from 'ngx-toastr';
import {take} from 'rxjs/operators';
@ -7,13 +7,15 @@ import {SettingsService} from '../settings.service';
import {ServerSettings} from '../_models/server-settings';
import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
import {NgTemplateOutlet, TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule, TranslocoService} from "@ngneat/transloco";
import {translate, TranslocoModule, TranslocoService} from "@jsverse/transloco";
import {WikiLink} from "../../_models/wiki";
import {PageLayoutModePipe} from "../../_pipes/page-layout-mode.pipe";
import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component";
import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {ConfirmService} from "../../shared/confirm.service";
import {debounceTime, distinctUntilChanged, filter, switchMap, tap} from "rxjs";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
const ValidIpAddress = /^(\s*((([12]?\d{1,2}\.){3}[12]?\d{1,2})|(([\da-f]{0,4}\:){0,7}([\da-f]{0,4})))\s*\,)*\s*((([12]?\d{1,2}\.){3}[12]?\d{1,2})|(([\da-f]{0,4}\:){0,7}([\da-f]{0,4})))\s*$/i;
@ -33,6 +35,7 @@ export class ManageSettingsComponent implements OnInit {
private readonly toastr = inject(ToastrService);
private readonly serverService = inject(ServerService);
private readonly confirmService = inject(ConfirmService);
private readonly destroyRef = inject(DestroyRef);
protected readonly WikiLink = WikiLink;
serverSettings!: ServerSettings;
@ -76,6 +79,23 @@ export class ManageSettingsComponent implements OnInit {
this.settingsForm.addControl('onDeckProgressDays', new FormControl(this.serverSettings.onDeckProgressDays, [Validators.required]));
this.settingsForm.addControl('onDeckUpdateDays', new FormControl(this.serverSettings.onDeckUpdateDays, [Validators.required]));
// Automatically save settings as we edit them
this.settingsForm.valueChanges.pipe(
distinctUntilChanged(),
debounceTime(100),
filter(_ => this.settingsForm.valid),
takeUntilDestroyed(this.destroyRef),
switchMap(_ => {
const data = this.packData();
return this.settingsService.updateServerSettings(data);
}),
tap(settings => {
this.serverSettings = settings;
this.resetForm();
this.cdRef.markForCheck();
})
).subscribe();
this.serverService.getServerInfo().subscribe(info => {
if (info.isDocker) {
this.settingsForm.get('ipAddresses')?.disable();
@ -89,42 +109,35 @@ export class ManageSettingsComponent implements OnInit {
}
resetForm() {
this.settingsForm.get('cacheDirectory')?.setValue(this.serverSettings.cacheDirectory);
this.settingsForm.get('scanTask')?.setValue(this.serverSettings.taskScan);
this.settingsForm.get('taskBackup')?.setValue(this.serverSettings.taskBackup);
this.settingsForm.get('taskCleanup')?.setValue(this.serverSettings.taskCleanup);
this.settingsForm.get('ipAddresses')?.setValue(this.serverSettings.ipAddresses);
this.settingsForm.get('port')?.setValue(this.serverSettings.port);
this.settingsForm.get('loggingLevel')?.setValue(this.serverSettings.loggingLevel);
this.settingsForm.get('allowStatCollection')?.setValue(this.serverSettings.allowStatCollection);
this.settingsForm.get('enableOpds')?.setValue(this.serverSettings.enableOpds);
this.settingsForm.get('baseUrl')?.setValue(this.serverSettings.baseUrl);
this.settingsForm.get('emailServiceUrl')?.setValue(this.serverSettings.emailServiceUrl);
this.settingsForm.get('totalBackups')?.setValue(this.serverSettings.totalBackups);
this.settingsForm.get('totalLogs')?.setValue(this.serverSettings.totalLogs);
this.settingsForm.get('enableFolderWatching')?.setValue(this.serverSettings.enableFolderWatching);
this.settingsForm.get('encodeMediaAs')?.setValue(this.serverSettings.encodeMediaAs);
this.settingsForm.get('hostName')?.setValue(this.serverSettings.hostName);
this.settingsForm.get('cacheSize')?.setValue(this.serverSettings.cacheSize);
this.settingsForm.get('onDeckProgressDays')?.setValue(this.serverSettings.onDeckProgressDays);
this.settingsForm.get('onDeckUpdateDays')?.setValue(this.serverSettings.onDeckUpdateDays);
this.settingsForm.get('cacheDirectory')?.setValue(this.serverSettings.cacheDirectory, {onlySelf: true, emitEvent: false});
this.settingsForm.get('scanTask')?.setValue(this.serverSettings.taskScan, {onlySelf: true, emitEvent: false});
this.settingsForm.get('taskBackup')?.setValue(this.serverSettings.taskBackup, {onlySelf: true, emitEvent: false});
this.settingsForm.get('taskCleanup')?.setValue(this.serverSettings.taskCleanup, {onlySelf: true, emitEvent: false});
this.settingsForm.get('ipAddresses')?.setValue(this.serverSettings.ipAddresses, {onlySelf: true, emitEvent: false});
this.settingsForm.get('port')?.setValue(this.serverSettings.port, {onlySelf: true, emitEvent: false});
this.settingsForm.get('loggingLevel')?.setValue(this.serverSettings.loggingLevel, {onlySelf: true, emitEvent: false});
this.settingsForm.get('allowStatCollection')?.setValue(this.serverSettings.allowStatCollection, {onlySelf: true, emitEvent: false});
this.settingsForm.get('enableOpds')?.setValue(this.serverSettings.enableOpds, {onlySelf: true, emitEvent: false});
this.settingsForm.get('baseUrl')?.setValue(this.serverSettings.baseUrl, {onlySelf: true, emitEvent: false});
this.settingsForm.get('emailServiceUrl')?.setValue(this.serverSettings.emailServiceUrl, {onlySelf: true, emitEvent: false});
this.settingsForm.get('totalBackups')?.setValue(this.serverSettings.totalBackups, {onlySelf: true, emitEvent: false});
this.settingsForm.get('totalLogs')?.setValue(this.serverSettings.totalLogs, {onlySelf: true, emitEvent: false});
this.settingsForm.get('enableFolderWatching')?.setValue(this.serverSettings.enableFolderWatching, {onlySelf: true, emitEvent: false});
this.settingsForm.get('encodeMediaAs')?.setValue(this.serverSettings.encodeMediaAs, {onlySelf: true, emitEvent: false});
this.settingsForm.get('hostName')?.setValue(this.serverSettings.hostName, {onlySelf: true, emitEvent: false});
this.settingsForm.get('cacheSize')?.setValue(this.serverSettings.cacheSize, {onlySelf: true, emitEvent: false});
this.settingsForm.get('onDeckProgressDays')?.setValue(this.serverSettings.onDeckProgressDays, {onlySelf: true, emitEvent: false});
this.settingsForm.get('onDeckUpdateDays')?.setValue(this.serverSettings.onDeckUpdateDays, {onlySelf: true, emitEvent: false});
this.settingsForm.markAsPristine();
this.cdRef.markForCheck();
}
async saveSettings() {
packData() {
const modelSettings = this.settingsForm.value;
modelSettings.bookmarksDirectory = this.serverSettings.bookmarksDirectory;
modelSettings.smtpConfig = this.serverSettings.smtpConfig;
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => {
this.serverSettings = settings;
this.resetForm();
this.toastr.success(this.translocoService.translate('toasts.server-settings-updated'));
}, (err: any) => {
console.error('error: ', err);
});
return modelSettings;
}
async resetToDefaults() {

View File

@ -2,7 +2,7 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} f
import {ServerService} from 'src/app/_services/server.service';
import {ServerInfoSlim} from '../_models/server-info';
import {DatePipe, NgIf} from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {ChangelogComponent} from "../../announcements/_components/changelog/changelog.component";
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";

View File

@ -125,8 +125,6 @@
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end">
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">{{t('reset-to-default')}}</button>
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()">{{t('reset')}}</button>
<button type="submit" class="flex-fill btn btn-primary" (click)="saveSettings()" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
</div>
<div class="setting-section-break"></div>

View File

@ -4,7 +4,7 @@ import {ToastrService} from 'ngx-toastr';
import {SettingsService} from '../settings.service';
import {ServerSettings} from '../_models/server-settings';
import {shareReplay, take} from 'rxjs/operators';
import {debounceTime, defer, forkJoin, Observable, of, switchMap, tap} from 'rxjs';
import {debounceTime, defer, distinctUntilChanged, filter, forkJoin, Observable, of, switchMap, tap} from 'rxjs';
import {ServerService} from 'src/app/_services/server.service';
import {Job} from 'src/app/_models/job/job';
import {UpdateNotificationModalComponent} from 'src/app/shared/update-notification/update-notification-modal.component';
@ -12,8 +12,8 @@ import {NgbModal, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
import {DownloadService} from 'src/app/shared/_services/download.service';
import {DefaultValuePipe} from '../../_pipes/default-value.pipe';
import {AsyncPipe, DatePipe, NgFor, NgIf, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule} from "@ngneat/transloco";
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
import {translate, TranslocoModule} from "@jsverse/transloco";
import {TranslocoLocaleModule} from "@jsverse/transloco-locale";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@ -136,6 +136,7 @@ export class ManageTasksSettingsComponent implements OnInit {
this.logLevels = result.levels;
this.serverSettings = result.settings;
this.settingsForm.addControl('taskScan', new FormControl(this.serverSettings.taskScan, [Validators.required]));
this.settingsForm.addControl('taskBackup', new FormControl(this.serverSettings.taskBackup, [Validators.required]));
this.settingsForm.addControl('taskCleanup', new FormControl(this.serverSettings.taskCleanup, [Validators.required]));
@ -203,6 +204,24 @@ export class ManageTasksSettingsComponent implements OnInit {
takeUntilDestroyed(this.destroyRef)
).subscribe();
// Automatically save settings as we edit them
this.settingsForm.valueChanges.pipe(
distinctUntilChanged(),
debounceTime(100),
filter(_ => this.settingsForm.valid),
takeUntilDestroyed(this.destroyRef),
switchMap(_ => {
const data = this.packData();
return this.settingsService.updateServerSettings(data);
}),
tap(settings => {
this.serverSettings = settings;
this.resetForm();
this.recurringTasks$ = this.serverService.getRecurringJobs().pipe(shareReplay());
this.cdRef.markForCheck();
})
).subscribe();
this.cdRef.markForCheck();
});
@ -212,33 +231,33 @@ export class ManageTasksSettingsComponent implements OnInit {
resetForm() {
this.settingsForm.get('taskScan')?.setValue(this.serverSettings.taskScan);
this.settingsForm.get('taskBackup')?.setValue(this.serverSettings.taskBackup);
this.settingsForm.get('taskCleanup')?.setValue(this.serverSettings.taskCleanup);
this.settingsForm.get('taskScan')?.setValue(this.serverSettings.taskScan, {onlySelf: true, emitEvent: false});
this.settingsForm.get('taskBackup')?.setValue(this.serverSettings.taskBackup, {onlySelf: true, emitEvent: false});
this.settingsForm.get('taskCleanup')?.setValue(this.serverSettings.taskCleanup, {onlySelf: true, emitEvent: false});
if (!this.taskFrequencies.includes(this.serverSettings.taskScan)) {
this.settingsForm.get('taskScanCustom')?.setValue(this.serverSettings.taskScan);
this.settingsForm.get('taskScanCustom')?.setValue(this.serverSettings.taskScan, {onlySelf: true, emitEvent: false});
} else {
this.settingsForm.get('taskScanCustom')?.setValue('');
this.settingsForm.get('taskScanCustom')?.setValue('', {onlySelf: true, emitEvent: false});
}
if (!this.taskFrequencies.includes(this.serverSettings.taskBackup)) {
this.settingsForm.get('taskBackupCustom')?.setValue(this.serverSettings.taskBackup);
this.settingsForm.get('taskBackupCustom')?.setValue(this.serverSettings.taskBackup, {onlySelf: true, emitEvent: false});
} else {
this.settingsForm.get('taskBackupCustom')?.setValue('');
this.settingsForm.get('taskBackupCustom')?.setValue('', {onlySelf: true, emitEvent: false});
}
if (!this.taskFrequencies.includes(this.serverSettings.taskCleanup)) {
this.settingsForm.get('taskCleanupCustom')?.setValue(this.serverSettings.taskCleanup);
this.settingsForm.get('taskCleanupCustom')?.setValue(this.serverSettings.taskCleanup, {onlySelf: true, emitEvent: false});
} else {
this.settingsForm.get('taskCleanupCustom')?.setValue('');
this.settingsForm.get('taskCleanupCustom')?.setValue('', {onlySelf: true, emitEvent: false});
}
this.settingsForm.markAsPristine();
this.cdRef.markForCheck();
}
async saveSettings() {
packData() {
const modelSettings = Object.assign({}, this.serverSettings);
modelSettings.taskBackup = this.settingsForm.get('taskBackup')?.value;
modelSettings.taskScan = this.settingsForm.get('taskScan')?.value;
@ -256,18 +275,10 @@ export class ManageTasksSettingsComponent implements OnInit {
modelSettings.taskCleanup = this.settingsForm.get('taskCleanupCustom')?.value;
}
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => {
this.serverSettings = settings;
this.resetForm();
this.recurringTasks$ = this.serverService.getRecurringJobs().pipe(shareReplay());
this.toastr.success(translate('toasts.server-settings-updated'));
this.cdRef.markForCheck();
}, (err: any) => {
console.error('error: ', err);
});
return modelSettings;
}
async resetToDefaults() {
if (!await this.confirmService.confirm(translate('toasts.confirm-reset-server-settings'))) return;

View File

@ -13,7 +13,7 @@ import {EditUserComponent} from '../edit-user/edit-user.component';
import {Router} from '@angular/router';
import {TagBadgeComponent} from '../../shared/tag-badge/tag-badge.component';
import {AsyncPipe, DatePipe, NgClass, NgIf, TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule, TranslocoService} from "@ngneat/transloco";
import {translate, TranslocoModule, TranslocoService} from "@jsverse/transloco";
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {ReadMoreComponent} from "../../shared/read-more/read-more.component";

View File

@ -13,7 +13,7 @@ import { User } from 'src/app/_models/user';
import {AccountService} from 'src/app/_services/account.service';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { NgFor } from '@angular/common';
import {TranslocoDirective,} from "@ngneat/transloco";
import {TranslocoDirective,} from "@jsverse/transloco";
import {SelectionModel} from "../../typeahead/_models/selection-model";
@Component({

View File

@ -1,8 +1,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {CommonModule} from '@angular/common';
import {JumpKey} from "../_models/jumpbar/jump-key";
import {EVENTS, Message, MessageHubService} from "../_services/message-hub.service";
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {CardItemComponent} from "../cards/card-item/card-item.component";
import {
SideNavCompanionBarComponent

View File

@ -29,7 +29,7 @@ import { CardDetailLayoutComponent } from '../../../cards/card-detail-layout/car
import { BulkOperationsComponent } from '../../../cards/bulk-operations/bulk-operations.component';
import { NgIf, DecimalPipe } from '@angular/common';
import { SideNavCompanionBarComponent } from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {SeriesFilterV2} from "../../../_models/metadata/v2/series-filter-v2";

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { ChangelogComponent } from '../changelog/changelog.component';
import { SideNavCompanionBarComponent } from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
@Component({
selector: 'app-announcements',

View File

@ -4,7 +4,7 @@ import {ServerService} from 'src/app/_services/server.service';
import {LoadingComponent} from '../../../shared/loading/loading.component';
import {ReadMoreComponent} from '../../../shared/read-more/read-more.component';
import {AsyncPipe, DatePipe} from '@angular/common';
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {AccountService} from "../../../_services/account.service";
@Component({

View File

@ -2,7 +2,7 @@ import {Component, DestroyRef, inject, Input} from '@angular/core';
import {FormsModule} from "@angular/forms";
import {AsyncPipe, NgForOf, NgIf} from "@angular/common";
import {NgbActiveModal, NgbHighlight, NgbModal, NgbTypeahead} from "@ng-bootstrap/ng-bootstrap";
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {ServerService} from "../../../_services/server.service";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {map} from "rxjs/operators";

View File

@ -61,6 +61,11 @@ const routes: Routes = [
pathMatch: 'full',
loadComponent: () => import('../app/series-detail/_components/series-detail/series-detail.component').then(c => c.SeriesDetailComponent)
},
{
path: ':libraryId/series/:seriesId/chapter/:chapterId',
pathMatch: 'full',
loadComponent: () => import('./chapter-detail/chapter-detail.component').then(c => c.ChapterDetailComponent)
},
{
path: ':libraryId/series/:seriesId/manga',
loadChildren: () => import('./_routes/manga-reader.router.module').then(m => m.routes)

View File

@ -1,6 +1,6 @@
<div [ngClass]="{'no-transitions' : (transitionState$ | async)}">
@if (accountService.currentUser$ | async; as currentUser) {
@if (currentUser) {
@if (currentUser && (navService.navbarVisible$ | async) === true) {
<div class="fullpage-background">
<div class="background-area">
<canvas id="backgroundCanvas" class="default-background" style="width: 100%; height: calc(var(--vh) * 100);"></canvas>

View File

@ -14,7 +14,7 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
import {ReaderService} from "../../../_services/reader.service";
import {ToastrService} from "ngx-toastr";
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {KEY_CODES} from "../../../shared/_services/utility.service";
enum BookLineOverlayMode {

View File

@ -51,7 +51,7 @@ import {
PersonalTableOfContentsComponent,
PersonalToCEvent
} from "../personal-table-of-contents/personal-table-of-contents.component";
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {translate, TranslocoDirective} from "@jsverse/transloco";
enum TabID {

View File

@ -13,7 +13,7 @@ import {ReaderService} from "../../../_services/reader.service";
import {PersonalToC} from "../../../_models/readers/personal-toc";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {TranslocoDirective} from "@ngneat/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
export interface PersonalToCEvent {
pageNum: number;

Some files were not shown because too many files have changed in this diff Show More