Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
Co-authored-by: Fesaa <77553571+Fesaa@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2025-02-20 19:29:05 -06:00 committed by GitHub
parent 158c119247
commit 83c63a7904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 126 additions and 61 deletions

View File

@ -631,4 +631,50 @@ public class ScannerServiceTests : AbstractDbTest
Assert.Contains(postLib.Series, s => s.Name == "Plush"); Assert.Contains(postLib.Series, s => s.Name == "Plush");
} }
[Fact]
public async Task ScanLibrary_DeleteSeriesInUI_ComeBack()
{
const string testcase = "Delete Series In UI - Manga.json";
// Setup: Generate test library
var infos = new Dictionary<string, ComicInfo>();
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
var testDirectoryPath = Path.Combine(Directory.GetCurrentDirectory(),
"../../../Services/Test Data/ScannerService/ScanTests",
testcase.Replace(".json", string.Empty));
library.Folders =
[
new FolderPath() { Path = Path.Combine(testDirectoryPath, "Root 1") },
new FolderPath() { Path = Path.Combine(testDirectoryPath, "Root 2") }
];
_unitOfWork.LibraryRepository.Update(library);
await _unitOfWork.CommitAsync();
var scanner = _scannerHelper.CreateServices();
// First Scan: Everything should be added
await scanner.ScanLibrary(library.Id);
var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series);
Assert.NotNull(postLib);
Assert.Contains(postLib.Series, s => s.Name == "Accel");
Assert.Contains(postLib.Series, s => s.Name == "Plush");
// Second Scan: Delete the Series
library.Series = [];
await _unitOfWork.CommitAsync();
postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series);
Assert.NotNull(postLib);
Assert.Empty(postLib.Series);
await scanner.ScanLibrary(library.Id);
postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series);
Assert.Contains(postLib.Series, s => s.Name == "Accel"); // Ensure Accel is gone
Assert.Contains(postLib.Series, s => s.Name == "Plush");
}
} }

View File

@ -0,0 +1,5 @@
[
"Root 1/Antarctic Press/Plush/Plush v01.cbz",
"Root 1/Antarctic Press/Plush/Plush v02.cbz",
"Root 2/Accel/Accel v01.cbz"
]

View File

@ -646,6 +646,7 @@ public class LibraryController : BaseApiController
library.ManageCollections = dto.ManageCollections; library.ManageCollections = dto.ManageCollections;
library.ManageReadingLists = dto.ManageReadingLists; library.ManageReadingLists = dto.ManageReadingLists;
library.AllowScrobbling = dto.AllowScrobbling; library.AllowScrobbling = dto.AllowScrobbling;
library.AllowMetadataMatching = dto.AllowMetadataMatching;
library.LibraryFileTypes = dto.FileGroupTypes library.LibraryFileTypes = dto.FileGroupTypes
.Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id}) .Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id})
.Distinct() .Distinct()

View File

@ -28,7 +28,7 @@ public interface IMetadataService
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)] [AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
Task GenerateCoversForLibrary(int libraryId, bool forceUpdate = false, bool forceColorScape = false); Task GenerateCoversForLibrary(int libraryId, bool forceUpdate = false, bool forceColorScape = false);
/// <summary> /// <summary>
/// Performs a forced refresh of cover images just for a series and it's nested entities /// Performs a forced refresh of cover images just for a series, and it's nested entities
/// </summary> /// </summary>
/// <param name="libraryId"></param> /// <param name="libraryId"></param>
/// <param name="seriesId"></param> /// <param name="seriesId"></param>
@ -75,12 +75,12 @@ public class MetadataService : IMetadataService
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param> /// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
/// <param name="encodeFormat">Convert image to Encoding Format when extracting the cover</param> /// <param name="encodeFormat">Convert image to Encoding Format when extracting the cover</param>
/// <param name="forceColorScape">Force colorscape gen</param> /// <param name="forceColorScape">Force colorscape gen</param>
private Task<bool> UpdateChapterCoverImage(Chapter? chapter, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceColorScape = false) private bool UpdateChapterCoverImage(Chapter? chapter, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceColorScape = false)
{ {
if (chapter == null) return Task.FromResult(false); if (chapter == null) return false;
var firstFile = chapter.Files.MinBy(x => x.Chapter); var firstFile = chapter.Files.MinBy(x => x.Chapter);
if (firstFile == null) return Task.FromResult(false); if (firstFile == null) return false;
if (!_cacheHelper.ShouldUpdateCoverImage( if (!_cacheHelper.ShouldUpdateCoverImage(
_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, chapter.CoverImage), _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, chapter.CoverImage),
@ -93,7 +93,7 @@ public class MetadataService : IMetadataService
_updateEvents.Add(MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter)); _updateEvents.Add(MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter));
} }
return Task.FromResult(false); return false;
} }
@ -107,7 +107,7 @@ public class MetadataService : IMetadataService
_unitOfWork.ChapterRepository.Update(chapter); _unitOfWork.ChapterRepository.Update(chapter);
_updateEvents.Add(MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter)); _updateEvents.Add(MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter));
return Task.FromResult(true); return true;
} }
private void UpdateChapterLastModified(Chapter chapter, bool forceUpdate) private void UpdateChapterLastModified(Chapter chapter, bool forceUpdate)
@ -135,10 +135,10 @@ public class MetadataService : IMetadataService
/// <param name="volume"></param> /// <param name="volume"></param>
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param> /// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
/// <param name="forceColorScape">Force updating colorscape</param> /// <param name="forceColorScape">Force updating colorscape</param>
private Task<bool> UpdateVolumeCoverImage(Volume? volume, bool forceUpdate, bool forceColorScape = false) private bool UpdateVolumeCoverImage(Volume? volume, bool forceUpdate, bool forceColorScape = false)
{ {
// We need to check if Volume coverImage matches first chapters if forceUpdate is false // We need to check if Volume coverImage matches first chapters if forceUpdate is false
if (volume == null) return Task.FromResult(false); if (volume == null) return false;
if (!_cacheHelper.ShouldUpdateCoverImage( if (!_cacheHelper.ShouldUpdateCoverImage(
_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, volume.CoverImage), _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, volume.CoverImage),
@ -150,7 +150,7 @@ public class MetadataService : IMetadataService
_unitOfWork.VolumeRepository.Update(volume); _unitOfWork.VolumeRepository.Update(volume);
_updateEvents.Add(MessageFactory.CoverUpdateEvent(volume.Id, MessageFactoryEntityTypes.Volume)); _updateEvents.Add(MessageFactory.CoverUpdateEvent(volume.Id, MessageFactoryEntityTypes.Volume));
} }
return Task.FromResult(false); return false;
} }
if (!volume.CoverImageLocked) if (!volume.CoverImageLocked)
@ -162,7 +162,7 @@ public class MetadataService : IMetadataService
if (firstChapter == null) if (firstChapter == null)
{ {
firstChapter = volume.Chapters.MinBy(x => x.SortOrder, ChapterSortComparerDefaultFirst.Default); firstChapter = volume.Chapters.MinBy(x => x.SortOrder, ChapterSortComparerDefaultFirst.Default);
if (firstChapter == null) return Task.FromResult(false); if (firstChapter == null) return false;
} }
volume.CoverImage = firstChapter.CoverImage; volume.CoverImage = firstChapter.CoverImage;
@ -171,7 +171,7 @@ public class MetadataService : IMetadataService
_updateEvents.Add(MessageFactory.CoverUpdateEvent(volume.Id, MessageFactoryEntityTypes.Volume)); _updateEvents.Add(MessageFactory.CoverUpdateEvent(volume.Id, MessageFactoryEntityTypes.Volume));
return Task.FromResult(true); return true;
} }
/// <summary> /// <summary>
@ -179,9 +179,9 @@ public class MetadataService : IMetadataService
/// </summary> /// </summary>
/// <param name="series"></param> /// <param name="series"></param>
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param> /// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
private Task UpdateSeriesCoverImage(Series? series, bool forceUpdate, bool forceColorScape = false) private void UpdateSeriesCoverImage(Series? series, bool forceUpdate, bool forceColorScape = false)
{ {
if (series == null) return Task.CompletedTask; if (series == null) return;
if (!_cacheHelper.ShouldUpdateCoverImage( if (!_cacheHelper.ShouldUpdateCoverImage(
_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, series.CoverImage), _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, series.CoverImage),
@ -194,7 +194,7 @@ public class MetadataService : IMetadataService
_updateEvents.Add(MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series)); _updateEvents.Add(MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series));
} }
return Task.CompletedTask; return;
} }
series.Volumes ??= []; series.Volumes ??= [];
@ -203,7 +203,6 @@ public class MetadataService : IMetadataService
_imageService.UpdateColorScape(series); _imageService.UpdateColorScape(series);
_updateEvents.Add(MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series)); _updateEvents.Add(MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series));
return Task.CompletedTask;
} }
@ -213,7 +212,7 @@ public class MetadataService : IMetadataService
/// <param name="series"></param> /// <param name="series"></param>
/// <param name="forceUpdate"></param> /// <param name="forceUpdate"></param>
/// <param name="encodeFormat"></param> /// <param name="encodeFormat"></param>
private async Task ProcessSeriesCoverGen(Series series, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceColorScape = false) private void ProcessSeriesCoverGen(Series series, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceColorScape = false)
{ {
_logger.LogDebug("[MetadataService] Processing cover image generation for series: {SeriesName}", series.OriginalName); _logger.LogDebug("[MetadataService] Processing cover image generation for series: {SeriesName}", series.OriginalName);
try try
@ -226,8 +225,8 @@ public class MetadataService : IMetadataService
var index = 0; var index = 0;
foreach (var chapter in volume.Chapters) foreach (var chapter in volume.Chapters)
{ {
var chapterUpdated = await UpdateChapterCoverImage(chapter, forceUpdate, encodeFormat, coverImageSize, forceColorScape); var chapterUpdated = UpdateChapterCoverImage(chapter, forceUpdate, encodeFormat, coverImageSize, forceColorScape);
// If cover was update, either the file has changed or first scan and we should force a metadata update // If cover was update, either the file has changed or first scan, and we should force a metadata update
UpdateChapterLastModified(chapter, forceUpdate || chapterUpdated); UpdateChapterLastModified(chapter, forceUpdate || chapterUpdated);
if (index == 0 && chapterUpdated) if (index == 0 && chapterUpdated)
{ {
@ -237,7 +236,7 @@ public class MetadataService : IMetadataService
index++; index++;
} }
var volumeUpdated = await UpdateVolumeCoverImage(volume, firstChapterUpdated || forceUpdate, forceColorScape); var volumeUpdated = UpdateVolumeCoverImage(volume, firstChapterUpdated || forceUpdate, forceColorScape);
if (volumeIndex == 0 && volumeUpdated) if (volumeIndex == 0 && volumeUpdated)
{ {
firstVolumeUpdated = true; firstVolumeUpdated = true;
@ -245,7 +244,7 @@ public class MetadataService : IMetadataService
volumeIndex++; volumeIndex++;
} }
await UpdateSeriesCoverImage(series, firstVolumeUpdated || forceUpdate, forceColorScape); UpdateSeriesCoverImage(series, firstVolumeUpdated || forceUpdate, forceColorScape);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -311,7 +310,7 @@ public class MetadataService : IMetadataService
try try
{ {
await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize, forceColorScape); ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize, forceColorScape);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -383,7 +382,7 @@ public class MetadataService : IMetadataService
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.CoverUpdateProgressEvent(series.LibraryId, 0F, ProgressEventType.Started, series.Name)); MessageFactory.CoverUpdateProgressEvent(series.LibraryId, 0F, ProgressEventType.Started, series.Name));
await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize, forceColorScape); ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize, forceColorScape);
if (_unitOfWork.HasChanges()) if (_unitOfWork.HasChanges())

View File

@ -537,8 +537,16 @@ public class CoverDbService : ICoverDbService
// Additional check to see if downloaded image is similar and we have a higher resolution // Additional check to see if downloaded image is similar and we have a higher resolution
if (chooseBetterImage) if (chooseBetterImage)
{ {
var betterImage = Path.Join(_directoryService.CoverImageDirectory, series.CoverImage).GetBetterImage(Path.Join(_directoryService.CoverImageDirectory, filePath))!; try
filePath = Path.GetFileName(betterImage); {
var betterImage = Path.Join(_directoryService.CoverImageDirectory, series.CoverImage)
.GetBetterImage(Path.Join(_directoryService.CoverImageDirectory, filePath))!;
filePath = Path.GetFileName(betterImage);
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an issue trying to choose a better cover image for Series: {SeriesName} ({SeriesId})", series.Name, series.Id);
}
} }
series.CoverImage = filePath; series.CoverImage = filePath;

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Data; using API.Data;
using API.DTOs.Theme; using API.DTOs.Theme;
@ -32,6 +33,7 @@ internal class GitHubContent
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonPropertyName("download_url")]
[JsonProperty("download_url")] [JsonProperty("download_url")]
public string DownloadUrl { get; set; } public string DownloadUrl { get; set; }
@ -151,6 +153,7 @@ public class ThemeService : IThemeService
// Fetch contents of the theme directory // Fetch contents of the theme directory
var themeContents = await GetDirectoryContent(themeDir.Path); var themeContents = await GetDirectoryContent(themeDir.Path);
// Find css and preview files // Find css and preview files
var cssFile = themeContents.FirstOrDefault(c => c.Name.EndsWith(".css")); var cssFile = themeContents.FirstOrDefault(c => c.Name.EndsWith(".css"));
var previewUrls = GetPreviewUrls(themeContents); var previewUrls = GetPreviewUrls(themeContents);
@ -187,7 +190,7 @@ public class ThemeService : IThemeService
return themeDtos; return themeDtos;
} }
private static IList<string> GetPreviewUrls(IEnumerable<GitHubContent> themeContents) private static List<string> GetPreviewUrls(IEnumerable<GitHubContent> themeContents)
{ {
return themeContents.Where(c => c.Name.ToLower().EndsWith(".jpg") || c.Name.ToLower().EndsWith(".png") ) return themeContents.Where(c => c.Name.ToLower().EndsWith(".jpg") || c.Name.ToLower().EndsWith(".png") )
.Select(p => p.DownloadUrl) .Select(p => p.DownloadUrl)
@ -196,10 +199,12 @@ public class ThemeService : IThemeService
private static async Task<IList<GitHubContent>> GetDirectoryContent(string path) private static async Task<IList<GitHubContent>> GetDirectoryContent(string path)
{ {
return await $"{GithubBaseUrl}/repos/Kareadita/Themes/contents/{path}" var json = await $"{GithubBaseUrl}/repos/Kareadita/Themes/contents/{path}"
.WithHeader("Accept", "application/vnd.github+json") .WithHeader("Accept", "application/vnd.github+json")
.WithHeader("User-Agent", "Kavita") .WithHeader("User-Agent", "Kavita")
.GetJsonAsync<List<GitHubContent>>(); .GetStringAsync();
return string.IsNullOrEmpty(json) ? [] : JsonConvert.DeserializeObject<List<GitHubContent>>(json);
} }
/// <summary> /// <summary>

View File

@ -54,7 +54,7 @@ export class ServerService {
} }
checkHowOutOfDate() { checkHowOutOfDate() {
return this.http.get<string>(this.baseUrl + 'server/checkHowOutOfDate', TextResonse) return this.http.get<string>(this.baseUrl + 'server/check-out-of-date', TextResonse)
.pipe(map(r => parseInt(r, 10))); .pipe(map(r => parseInt(r, 10)));
} }

View File

@ -9,7 +9,7 @@
<form [formGroup]="formGroup"> <form [formGroup]="formGroup">
<div class="mt-2"> <div class="mt-2">
<app-setting-item [title]="t('title')" (editMode)="updateEditMode($event)" [isEditMode]="!isViewMode" [showEdit]="hasLicense"> <app-setting-item [title]="t('title')" (editMode)="updateEditMode($event)" [isEditMode]="!isViewMode" [showEdit]="hasLicense" [fixedExtras]="true">
<ng-template #titleExtra> <ng-template #titleExtra>
<button class="btn btn-icon btn-sm" (click)="loadLicenseInfo(true)"> <button class="btn btn-icon btn-sm" (click)="loadLicenseInfo(true)">
@if (isChecking) { @if (isChecking) {
@ -75,10 +75,10 @@
</div> </div>
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3"> <div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
<!-- <button type="button" class="flex-fill btn btn-danger me-1" aria-describedby="license-key-header"--> <button type="button" class="flex-fill btn btn-danger me-1" aria-describedby="license-key-header"
<!-- (click)="deleteLicense()">--> (click)="deleteLicense()">
<!-- {{t('activate-delete')}}--> {{t('activate-delete')}}
<!-- </button>--> </button>
<button type="button" class="flex-fill btn btn-danger me-1" aria-describedby="license-key-header" <button type="button" class="flex-fill btn btn-danger me-1" aria-describedby="license-key-header"
[ngbTooltip]="t('activate-reset-tooltip')" [ngbTooltip]="t('activate-reset-tooltip')"
[disabled]="!formGroup.get('email')?.value || !formGroup.get('licenseKey')?.value" (click)="resetLicense()"> [disabled]="!formGroup.get('email')?.value || !formGroup.get('licenseKey')?.value" (click)="resetLicense()">

View File

@ -1,7 +1,7 @@
<ng-container *transloco="let t;"> <ng-container *transloco="let t;">
<div> <div>
<div class="settings-row g-0 row"> <div class="settings-row g-0 row">
<div class="col-auto setting-title edit"> <div class="col-auto {{fixedExtras ? 'setting-title no-anim edit' : 'setting-title edit'}}">
<h6 class="section-title"> <h6 class="section-title">
@if (labelId) { @if (labelId) {
<label class="reset-label" [for]="labelId">{{title}}</label> <label class="reset-label" [for]="labelId">{{title}}</label>

View File

@ -12,23 +12,23 @@
cursor: pointer; cursor: pointer;
} }
.setting-title.edit:hover ~ .edit-btn{ //.setting-title.no-anim.edit:hover ~ .edit-btn {
opacity: 1; // opacity: 1;
transition: opacity 0.3s ease-out; // transition: none;
} //}
//
.edit-btn { //.edit-btn {
opacity: 0; // opacity: 0;
transition: opacity 0.5s ease-out; // transition: opacity 0.5s ease-out;
transition-delay: 0.5s; // transition-delay: 0.5s;
//
&:hover { // &:hover {
opacity: 1; // opacity: 1;
transition: opacity 0.3s ease-out; // transition: opacity 0.3s ease-out;
} // }
//}
.btn { //
margin-bottom: 0.5em; //.setting-title.no-anim + .edit-btn,
line-height: 1.2; //.setting-title.no-anim.edit:hover ~ .edit-btn {
} // transition: none !important;
} //}

View File

@ -11,7 +11,7 @@ import {TranslocoDirective} from "@jsverse/transloco";
import {NgTemplateOutlet} from "@angular/common"; import {NgTemplateOutlet} from "@angular/common";
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe"; import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
import {filter, fromEvent, tap} from "rxjs"; import {filter, fromEvent, tap} from "rxjs";
import {AbstractControl, FormControl} from "@angular/forms"; import {AbstractControl} from "@angular/forms";
@Component({ @Component({
selector: 'app-setting-item', selector: 'app-setting-item',
@ -37,6 +37,10 @@ export class SettingItemComponent implements OnChanges {
@Input() subtitle: string | undefined = undefined; @Input() subtitle: string | undefined = undefined;
@Input() labelId: string | undefined = undefined; @Input() labelId: string | undefined = undefined;
@Input() toggleOnViewClick: boolean = true; @Input() toggleOnViewClick: boolean = true;
/**
* When true, the hover animation will not be present and the titleExtras will be always visible
*/
@Input() fixedExtras: boolean = false;
@Input() control: AbstractControl<any> | null = null; @Input() control: AbstractControl<any> | null = null;
@Output() editMode = new EventEmitter<boolean>(); @Output() editMode = new EventEmitter<boolean>();

View File

@ -12,17 +12,14 @@ import {SiteTheme, ThemeProvider} from 'src/app/_models/preferences/site-theme';
import { User } from 'src/app/_models/user'; import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service'; import { AccountService } from 'src/app/_services/account.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import { SiteThemeProviderPipe } from '../../_pipes/site-theme-provider.pipe';
import { SentenceCasePipe } from '../../_pipes/sentence-case.pipe'; import { SentenceCasePipe } from '../../_pipes/sentence-case.pipe';
import { AsyncPipe, NgTemplateOutlet} from '@angular/common'; import { AsyncPipe, NgTemplateOutlet} from '@angular/common';
import {translate, TranslocoDirective} from "@jsverse/transloco"; import {translate, TranslocoDirective} from "@jsverse/transloco";
import {shareReplay} from "rxjs/operators"; import {shareReplay} from "rxjs/operators";
import {CarouselReelComponent} from "../../carousel/_components/carousel-reel/carousel-reel.component"; import {CarouselReelComponent} from "../../carousel/_components/carousel-reel/carousel-reel.component";
import {SeriesCardComponent} from "../../cards/series-card/series-card.component";
import {ImageComponent} from "../../shared/image/image.component"; import {ImageComponent} from "../../shared/image/image.component";
import {DownloadableSiteTheme} from "../../_models/theme/downloadable-site-theme"; import {DownloadableSiteTheme} from "../../_models/theme/downloadable-site-theme";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe"; import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {SafeUrlPipe} from "../../_pipes/safe-url.pipe";
import {ScrobbleProvider} from "../../_services/scrobbling.service"; import {ScrobbleProvider} from "../../_services/scrobbling.service";
import {ConfirmService} from "../../shared/confirm.service"; import {ConfirmService} from "../../shared/confirm.service";
import {FileSystemFileEntry, NgxFileDropEntry, NgxFileDropModule} from "ngx-file-drop"; import {FileSystemFileEntry, NgxFileDropEntry, NgxFileDropModule} from "ngx-file-drop";
@ -46,8 +43,8 @@ interface ThemeContainer {
styleUrls: ['./theme-manager.component.scss'], styleUrls: ['./theme-manager.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [AsyncPipe, SentenceCasePipe, SiteThemeProviderPipe, TranslocoDirective, CarouselReelComponent, imports: [AsyncPipe, SentenceCasePipe, TranslocoDirective, CarouselReelComponent,
SeriesCardComponent, ImageComponent, DefaultValuePipe, NgTemplateOutlet, SafeUrlPipe, NgxFileDropModule, ImageComponent, DefaultValuePipe, NgTemplateOutlet, NgxFileDropModule,
ReactiveFormsModule, Select2Module, LoadingComponent] ReactiveFormsModule, Select2Module, LoadingComponent]
}) })
export class ThemeManagerComponent { export class ThemeManagerComponent {

View File

@ -2,7 +2,7 @@
"openapi": "3.0.1", "openapi": "3.0.1",
"info": { "info": {
"title": "Kavita", "title": "Kavita",
"description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.4.16", "description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.4.17",
"license": { "license": {
"name": "GPL-3.0", "name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"