Release Polish 3 (#3359)

This commit is contained in:
Joe Milazzo 2024-11-12 13:04:43 -06:00 committed by GitHub
parent dd3dec269f
commit f812f61001
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 211 additions and 134 deletions

View File

@ -42,10 +42,15 @@ public class PersonController : BaseApiController
return Ok(await _unitOfWork.PersonRepository.GetPersonDtoByName(name, User.GetUserId())); return Ok(await _unitOfWork.PersonRepository.GetPersonDtoByName(name, User.GetUserId()));
} }
/// <summary>
/// Returns all roles for a Person
/// </summary>
/// <param name="personId"></param>
/// <returns></returns>
[HttpGet("roles")] [HttpGet("roles")]
public async Task<ActionResult<IEnumerable<PersonRole>>> GetRolesForPersonByName(string name) public async Task<ActionResult<IEnumerable<PersonRole>>> GetRolesForPersonByName(int personId)
{ {
return Ok(await _unitOfWork.PersonRepository.GetRolesForPersonByName(name, User.GetUserId())); return Ok(await _unitOfWork.PersonRepository.GetRolesForPersonByName(personId, User.GetUserId()));
} }
/// <summary> /// <summary>

View File

@ -490,23 +490,27 @@ public class UploadController : BaseApiController
[HttpPost("person")] [HttpPost("person")]
public async Task<ActionResult> UploadPersonCoverImageFromUrl(UploadFileDto uploadFileDto) public async Task<ActionResult> UploadPersonCoverImageFromUrl(UploadFileDto uploadFileDto)
{ {
// Check if Url is non-empty, request the image and place in temp, then ask image service to handle it.
// See if we can do this all in memory without touching underlying system
if (string.IsNullOrEmpty(uploadFileDto.Url))
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-required"));
}
try try
{ {
var person = await _unitOfWork.PersonRepository.GetPersonById(uploadFileDto.Id); var person = await _unitOfWork.PersonRepository.GetPersonById(uploadFileDto.Id);
if (person == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "person-doesnt-exist")); if (person == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "person-doesnt-exist"));
var filePath = await CreateThumbnail(uploadFileDto, $"{ImageService.GetPersonFormat(uploadFileDto.Id)}");
if (!string.IsNullOrEmpty(filePath)) if (!string.IsNullOrEmpty(uploadFileDto.Url))
{ {
person.CoverImage = filePath; var filePath = await CreateThumbnail(uploadFileDto, $"{ImageService.GetPersonFormat(uploadFileDto.Id)}");
person.CoverImageLocked = true;
if (!string.IsNullOrEmpty(filePath))
{
person.CoverImage = filePath;
person.CoverImageLocked = true;
_imageService.UpdateColorScape(person);
_unitOfWork.PersonRepository.Update(person);
}
}
else
{
person.CoverImage = string.Empty;
person.CoverImageLocked = false;
_imageService.UpdateColorScape(person); _imageService.UpdateColorScape(person);
_unitOfWork.PersonRepository.Update(person); _unitOfWork.PersonRepository.Update(person);
} }

View File

@ -33,7 +33,7 @@ public interface IPersonRepository
Task<string> GetCoverImageAsync(int personId); Task<string> GetCoverImageAsync(int personId);
Task<string?> GetCoverImageByNameAsync(string name); Task<string?> GetCoverImageByNameAsync(string name);
Task<IEnumerable<PersonRole>> GetRolesForPersonByName(string name, int userId); Task<IEnumerable<PersonRole>> GetRolesForPersonByName(int personId, int userId);
Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams); Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams);
Task<Person?> GetPersonById(int personId); Task<Person?> GetPersonById(int personId);
Task<PersonDto?> GetPersonDtoByName(string name, int userId); Task<PersonDto?> GetPersonDtoByName(string name, int userId);
@ -145,18 +145,28 @@ public class PersonRepository : IPersonRepository
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
} }
public async Task<IEnumerable<PersonRole>> GetRolesForPersonByName(string name, int userId) public async Task<IEnumerable<PersonRole>> GetRolesForPersonByName(int personId, int userId)
{ {
// TODO: This will need to check both series and chapters (in cases where komf only updates series)
var normalized = name.ToNormalized();
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
return await _context.Person // Query roles from ChapterPeople
.Where(p => p.NormalizedName == normalized) var chapterRoles = await _context.Person
.Where(p => p.Id == personId)
.RestrictAgainstAgeRestriction(ageRating) .RestrictAgainstAgeRestriction(ageRating)
.SelectMany(p => p.ChapterPeople.Select(cp => cp.Role)) .SelectMany(p => p.ChapterPeople.Select(cp => cp.Role))
.Distinct() .Distinct()
.ToListAsync(); .ToListAsync();
// Query roles from SeriesMetadataPeople
var seriesRoles = await _context.Person
.Where(p => p.Id == personId)
.RestrictAgainstAgeRestriction(ageRating)
.SelectMany(p => p.SeriesMetadataPeople.Select(smp => smp.Role))
.Distinct()
.ToListAsync();
// Combine and return distinct roles
return chapterRoles.Union(seriesRoles).Distinct();
} }
public async Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams) public async Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams)

View File

@ -1046,8 +1046,6 @@ public class SeriesRepository : ISeriesRepository
.Select(u => u.CollapseSeriesRelationships) .Select(u => u.CollapseSeriesRelationships)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
query ??= _context.Series query ??= _context.Series
.AsNoTracking(); .AsNoTracking();
@ -1064,7 +1062,6 @@ public class SeriesRepository : ISeriesRepository
query = ApplyWantToReadFilter(filter, query, userId); query = ApplyWantToReadFilter(filter, query, userId);
query = await ApplyCollectionFilter(filter, query, userId, userRating); query = await ApplyCollectionFilter(filter, query, userId, userRating);
@ -1136,6 +1133,7 @@ public class SeriesRepository : ISeriesRepository
var seriesIds = _context.AppUser.Where(u => u.Id == userId) var seriesIds = _context.AppUser.Where(u => u.Id == userId)
.SelectMany(u => u.WantToRead) .SelectMany(u => u.WantToRead)
.Select(s => s.SeriesId); .Select(s => s.SeriesId);
if (bool.Parse(wantToReadStmt.Value)) if (bool.Parse(wantToReadStmt.Value))
{ {
query = query.Where(s => seriesIds.Contains(s.Id)); query = query.Where(s => seriesIds.Contains(s.Id));
@ -1152,6 +1150,7 @@ public class SeriesRepository : ISeriesRepository
{ {
var filterIncludeLibs = new List<int>(); var filterIncludeLibs = new List<int>();
var filterExcludeLibs = new List<int>(); var filterExcludeLibs = new List<int>();
if (filter.Statements != null) if (filter.Statements != null)
{ {
foreach (var stmt in filter.Statements.Where(stmt => stmt.Field == FilterField.Libraries)) foreach (var stmt in filter.Statements.Where(stmt => stmt.Field == FilterField.Libraries))
@ -1993,17 +1992,25 @@ public class SeriesRepository : ISeriesRepository
public async Task<PagedList<SeriesDto>> GetWantToReadForUserV2Async(int userId, UserParams userParams, FilterV2Dto filter) public async Task<PagedList<SeriesDto>> GetWantToReadForUserV2Async(int userId, UserParams userParams, FilterV2Dto filter)
{ {
var libraryIds = await _context.Library.GetUserLibraries(userId).ToListAsync(); var libraryIds = await _context.Library.GetUserLibraries(userId).ToListAsync();
var query = _context.AppUser var seriesIds = await _context.AppUser
.Where(user => user.Id == userId) .Where(user => user.Id == userId)
.SelectMany(u => u.WantToRead) .SelectMany(u => u.WantToRead)
.Where(s => libraryIds.Contains(s.Series.LibraryId)) .Where(s => libraryIds.Contains(s.Series.LibraryId))
.Select(w => w.Series) .Select(w => w.Series.Id)
.Distinct()
.ToListAsync();
var query = await CreateFilteredSearchQueryableV2(userId, filter, QueryContext.None);
// Apply the Want to Read filtering
query = query.Where(s => seriesIds.Contains(s.Id));
var retSeries = query
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
.AsSplitQuery() .AsSplitQuery()
.AsNoTracking(); .AsNoTracking();
var filteredQuery = await CreateFilteredSearchQueryableV2(userId, filter, QueryContext.None, query); return await PagedList<SeriesDto>.CreateAsync(retSeries, userParams.PageNumber, userParams.PageSize);
return await PagedList<SeriesDto>.CreateAsync(filteredQuery.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider), userParams.PageNumber, userParams.PageSize);
} }
public async Task<IList<Series>> GetWantToReadForUserAsync(int userId) public async Task<IList<Series>> GetWantToReadForUserAsync(int userId)

View File

@ -259,8 +259,7 @@ public static class SeriesFilter
.Where(p => p != null && p.AppUserId == userId) .Where(p => p != null && p.AppUserId == userId)
.Sum(p => p != null ? (p.PagesRead * 1.0f / s.Pages) : 0) * 100 .Sum(p => p != null ? (p.PagesRead * 1.0f / s.Pages) : 0) * 100
}) })
.AsSplitQuery() .AsSplitQuery();
.AsEnumerable();
switch (comparison) switch (comparison)
{ {

View File

@ -79,7 +79,6 @@ public static class QueryableExtensions
.Include(l => l.AppUsers) .Include(l => l.AppUsers)
.Where(lib => lib.AppUsers.Any(user => user.Id == userId)) .Where(lib => lib.AppUsers.Any(user => user.Id == userId))
.IsRestricted(queryContext) .IsRestricted(queryContext)
.AsNoTracking()
.AsSplitQuery() .AsSplitQuery()
.Select(lib => lib.Id); .Select(lib => lib.Id);
} }

View File

@ -585,6 +585,7 @@ public class ImageService : IImageService
/// <inheritdoc /> /// <inheritdoc />
public string CreateThumbnailFromBase64(string encodedImage, string fileName, EncodeFormat encodeFormat, int thumbnailWidth = ThumbnailWidth) public string CreateThumbnailFromBase64(string encodedImage, string fileName, EncodeFormat encodeFormat, int thumbnailWidth = ThumbnailWidth)
{ {
// TODO: This code has no concept of cropping nor Thumbnail Size
try try
{ {
using var thumbnail = Image.ThumbnailBuffer(Convert.FromBase64String(encodedImage), thumbnailWidth); using var thumbnail = Image.ThumbnailBuffer(Convert.FromBase64String(encodedImage), thumbnailWidth);

View File

@ -334,7 +334,10 @@ public class SeriesService : ISeriesService
private async Task HandlePeopleUpdateAsync(SeriesMetadata metadata, ICollection<PersonDto> peopleDtos, PersonRole role) private async Task HandlePeopleUpdateAsync(SeriesMetadata metadata, ICollection<PersonDto> peopleDtos, PersonRole role)
{ {
// Normalize all names from the DTOs // Normalize all names from the DTOs
var normalizedNames = peopleDtos.Select(p => Parser.Normalize(p.Name)).ToList(); var normalizedNames = peopleDtos
.Select(p => Parser.Normalize(p.Name))
.Distinct()
.ToList();
// Bulk select people who already exist in the database // Bulk select people who already exist in the database
var existingPeople = await _unitOfWork.PersonRepository.GetPeopleByNames(normalizedNames); var existingPeople = await _unitOfWork.PersonRepository.GetPeopleByNames(normalizedNames);

View File

@ -83,6 +83,7 @@ public class TaskScheduler : ITaskScheduler
public static readonly ImmutableArray<string> ScanTasks = public static readonly ImmutableArray<string> ScanTasks =
["ScannerService", "ScanLibrary", "ScanLibraries", "ScanFolder", "ScanSeries"]; ["ScannerService", "ScanLibrary", "ScanLibraries", "ScanFolder", "ScanSeries"];
private static readonly ImmutableArray<string> NonCronOptions = ["disabled", "daily", "weekly"];
private static readonly Random Rnd = new Random(); private static readonly Random Rnd = new Random();
@ -122,10 +123,10 @@ public class TaskScheduler : ITaskScheduler
public async Task ScheduleTasks() public async Task ScheduleTasks()
{ {
_logger.LogInformation("Scheduling reoccurring tasks"); _logger.LogInformation("Scheduling reoccurring tasks");
var nonCronOptions = new List<string>(["disabled", "daily", "weekly"]);
var setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Value; var setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Value;
if (setting == null || (!nonCronOptions.Contains(setting) && !CronHelper.IsValidCron(setting))) if (IsInvalidCronSetting(setting))
{ {
_logger.LogError("Scan Task has invalid cron, defaulting to Daily"); _logger.LogError("Scan Task has invalid cron, defaulting to Daily");
RecurringJob.AddOrUpdate(ScanLibrariesTaskId, () => ScanLibraries(false), RecurringJob.AddOrUpdate(ScanLibrariesTaskId, () => ScanLibraries(false),
@ -141,9 +142,9 @@ public class TaskScheduler : ITaskScheduler
setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskBackup)).Value; setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskBackup)).Value;
if (setting == null || (!nonCronOptions.Contains(setting) && !CronHelper.IsValidCron(setting))) if (IsInvalidCronSetting(setting))
{ {
_logger.LogError("Backup Task has invalid cron, defaulting to Daily"); _logger.LogError("Backup Task has invalid cron, defaulting to Weekly");
RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(), RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(),
Cron.Weekly, RecurringJobOptions); Cron.Weekly, RecurringJobOptions);
} }
@ -161,18 +162,18 @@ public class TaskScheduler : ITaskScheduler
} }
setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskCleanup)).Value; setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskCleanup)).Value;
if (setting == null || (!nonCronOptions.Contains(setting) && !CronHelper.IsValidCron(setting))) if (IsInvalidCronSetting(setting))
{
_logger.LogDebug("Scheduling Cleanup Task for {Setting}", setting);
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(),
CronConverter.ConvertToCronNotation(setting), RecurringJobOptions);
}
else
{ {
_logger.LogError("Cleanup Task has invalid cron, defaulting to Daily"); _logger.LogError("Cleanup Task has invalid cron, defaulting to Daily");
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(), RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(),
Cron.Daily, RecurringJobOptions); Cron.Daily, RecurringJobOptions);
} }
else
{
_logger.LogDebug("Scheduling Cleanup Task for {Setting}", setting);
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(),
CronConverter.ConvertToCronNotation(setting), RecurringJobOptions);
}
RecurringJob.AddOrUpdate(RemoveFromWantToReadTaskId, () => _cleanupService.CleanupWantToRead(), RecurringJob.AddOrUpdate(RemoveFromWantToReadTaskId, () => _cleanupService.CleanupWantToRead(),
@ -186,6 +187,11 @@ public class TaskScheduler : ITaskScheduler
await ScheduleKavitaPlusTasks(); await ScheduleKavitaPlusTasks();
} }
private static bool IsInvalidCronSetting(string setting)
{
return setting == null || (!NonCronOptions.Contains(setting) && !CronHelper.IsValidCron(setting));
}
public async Task ScheduleKavitaPlusTasks() public async Task ScheduleKavitaPlusTasks()
{ {
// KavitaPlus based (needs license check) // KavitaPlus based (needs license check)

View File

@ -0,0 +1 @@
export const DefaultModalOptions = {scrollable: true, size: 'xl', fullscreen: 'xl'};

View File

@ -1,6 +1,6 @@
import {inject, Pipe, PipeTransform} from '@angular/core'; import {inject, Pipe, PipeTransform} from '@angular/core';
import { PersonRole } from '../_models/metadata/person'; import { PersonRole } from '../_models/metadata/person';
import {TranslocoService} from "@jsverse/transloco"; import {translate, TranslocoService} from "@jsverse/transloco";
@Pipe({ @Pipe({
name: 'personRole', name: 'personRole',
@ -8,39 +8,38 @@ import {TranslocoService} from "@jsverse/transloco";
}) })
export class PersonRolePipe implements PipeTransform { export class PersonRolePipe implements PipeTransform {
translocoService = inject(TranslocoService);
transform(value: PersonRole): string { transform(value: PersonRole): string {
switch (value) { switch (value) {
case PersonRole.Artist: case PersonRole.Artist:
return this.translocoService.translate('person-role-pipe.artist'); return translate('person-role-pipe.artist');
case PersonRole.Character: case PersonRole.Character:
return this.translocoService.translate('person-role-pipe.character'); return translate('person-role-pipe.character');
case PersonRole.Colorist: case PersonRole.Colorist:
return this.translocoService.translate('person-role-pipe.colorist'); return translate('person-role-pipe.colorist');
case PersonRole.CoverArtist: case PersonRole.CoverArtist:
return this.translocoService.translate('person-role-pipe.artist'); return translate('person-role-pipe.artist');
case PersonRole.Editor: case PersonRole.Editor:
return this.translocoService.translate('person-role-pipe.editor'); return translate('person-role-pipe.editor');
case PersonRole.Inker: case PersonRole.Inker:
return this.translocoService.translate('person-role-pipe.inker'); return translate('person-role-pipe.inker');
case PersonRole.Letterer: case PersonRole.Letterer:
return this.translocoService.translate('person-role-pipe.letterer'); return translate('person-role-pipe.letterer');
case PersonRole.Penciller: case PersonRole.Penciller:
return this.translocoService.translate('person-role-pipe.penciller'); return translate('person-role-pipe.penciller');
case PersonRole.Publisher: case PersonRole.Publisher:
return this.translocoService.translate('person-role-pipe.publisher'); return translate('person-role-pipe.publisher');
case PersonRole.Imprint: case PersonRole.Imprint:
return this.translocoService.translate('person-role-pipe.imprint'); return translate('person-role-pipe.imprint');
case PersonRole.Writer: case PersonRole.Writer:
return this.translocoService.translate('person-role-pipe.writer'); return translate('person-role-pipe.writer');
case PersonRole.Team: case PersonRole.Team:
return this.translocoService.translate('person-role-pipe.team'); return translate('person-role-pipe.team');
case PersonRole.Location: case PersonRole.Location:
return this.translocoService.translate('person-role-pipe.location'); return translate('person-role-pipe.location');
case PersonRole.Translator: case PersonRole.Translator:
return this.translocoService.translate('person-role-pipe.translator'); return translate('person-role-pipe.translator');
case PersonRole.Other: case PersonRole.Other:
return this.translocoService.translate('person-role-pipe.other'); return translate('person-role-pipe.other');
default: default:
return ''; return '';
} }

View File

@ -27,6 +27,7 @@ import {FilterService} from "./filter.service";
import {ReadingListService} from "./reading-list.service"; import {ReadingListService} from "./reading-list.service";
import {ChapterService} from "./chapter.service"; import {ChapterService} from "./chapter.service";
import {VolumeService} from "./volume.service"; import {VolumeService} from "./volume.service";
import {DefaultModalOptions} from "../_models/default-modal-options";
export type LibraryActionCallback = (library: Partial<Library>) => void; export type LibraryActionCallback = (library: Partial<Library>) => void;
export type SeriesActionCallback = (series: Series) => void; export type SeriesActionCallback = (series: Series) => void;
@ -121,7 +122,7 @@ export class ActionService {
} }
editLibrary(library: Partial<Library>, callback?: LibraryActionCallback) { editLibrary(library: Partial<Library>, callback?: LibraryActionCallback) {
const modalRef = this.modalService.open(LibrarySettingsModalComponent, {size: 'xl', fullscreen: 'md'}); const modalRef = this.modalService.open(LibrarySettingsModalComponent, DefaultModalOptions);
modalRef.componentInstance.library = library; modalRef.componentInstance.library = library;
modalRef.closed.subscribe((closeResult: {success: boolean, library: Library, coverImageUpdate: boolean}) => { modalRef.closed.subscribe((closeResult: {success: boolean, library: Library, coverImageUpdate: boolean}) => {
if (callback) callback(library) if (callback) callback(library)

View File

@ -16,7 +16,8 @@ export class JumpbarService {
getResumeKey(key: string) { getResumeKey(key: string) {
if (this.resumeKeys.hasOwnProperty(key)) return this.resumeKeys[key]; const k = key.toUpperCase();
if (this.resumeKeys.hasOwnProperty(k)) return this.resumeKeys[k];
return ''; return '';
} }
@ -26,7 +27,8 @@ export class JumpbarService {
} }
saveResumeKey(key: string, value: string) { saveResumeKey(key: string, value: string) {
this.resumeKeys[key] = value; const k = key.toUpperCase();
this.resumeKeys[k] = value;
} }
saveResumePosition(url: string, value: number) { saveResumePosition(url: string, value: number) {
@ -75,9 +77,11 @@ export class JumpbarService {
_removeFirstPartOfJumpBar(midPoint: number, numberOfRemovals: number = 1, jumpBarKeys: Array<JumpKey>, jumpBarKeysToRender: Array<JumpKey>) { _removeFirstPartOfJumpBar(midPoint: number, numberOfRemovals: number = 1, jumpBarKeys: Array<JumpKey>, jumpBarKeysToRender: Array<JumpKey>) {
const removedIndexes: Array<number> = []; const removedIndexes: Array<number> = [];
for(let removal = 0; removal < numberOfRemovals; removal++) { for(let removal = 0; removal < numberOfRemovals; removal++) {
let min = 100000000; let min = 100000000;
let minIndex = -1; let minIndex = -1;
for(let i = 1; i < midPoint; i++) { for(let i = 1; i < midPoint; i++) {
if (jumpBarKeys[i].size < min && !removedIndexes.includes(i)) { if (jumpBarKeys[i].size < min && !removedIndexes.includes(i)) {
min = jumpBarKeys[i].size; min = jumpBarKeys[i].size;
@ -101,7 +105,7 @@ export class JumpbarService {
getJumpKeys(data :Array<any>, keySelector: (data: any) => string) { getJumpKeys(data :Array<any>, keySelector: (data: any) => string) {
const keys: {[key: string]: number} = {}; const keys: {[key: string]: number} = {};
data.forEach(obj => { data.forEach(obj => {
let ch = keySelector(obj).charAt(0); let ch = keySelector(obj).charAt(0).toUpperCase();
if (/\d|\#|!|%|@|\(|\)|\^|\.|_|\*/g.test(ch)) { if (/\d|\#|!|%|@|\(|\)|\^|\.|_|\*/g.test(ch)) {
ch = '#'; ch = '#';
} }
@ -111,10 +115,11 @@ export class JumpbarService {
keys[ch] += 1; keys[ch] += 1;
}); });
return Object.keys(keys).map(k => { return Object.keys(keys).map(k => {
k = k.toUpperCase();
return { return {
key: k, key: k,
size: keys[k], size: keys[k],
title: k.toUpperCase() title: k
} }
}).sort((a, b) => { }).sort((a, b) => {
if (a.key < b.key) return -1; if (a.key < b.key) return -1;

View File

@ -29,8 +29,8 @@ export class PersonService {
return this.httpClient.get<Person>(this.baseUrl + `person?name=${name}`); return this.httpClient.get<Person>(this.baseUrl + `person?name=${name}`);
} }
getRolesForPerson(name: string) { getRolesForPerson(personId: number) {
return this.httpClient.get<Array<PersonRole>>(this.baseUrl + `person/roles?name=${name}`); return this.httpClient.get<Array<PersonRole>>(this.baseUrl + `person/roles?personId=${personId}`);
} }
getSeriesMostKnownFor(personId: number) { getSeriesMostKnownFor(personId: number) {

View File

@ -4,7 +4,7 @@
<div class="publisher-side publisher-front"> <div class="publisher-side publisher-front">
<div class="publisher-img-container d-inline-flex align-items-center me-2 position-relative"> <div class="publisher-img-container d-inline-flex align-items-center me-2 position-relative">
<app-image <app-image
[imageUrl]="imageService.getPersonImage(currentPublisher!.id)" [imageUrl]="imageService.getPublisherImage(currentPublisher!.name)"
[classes]="'me-2'" [classes]="'me-2'"
[hideOnError]="true" [hideOnError]="true"
width="32px" width="32px"
@ -20,7 +20,7 @@
<div class="publisher-side publisher-back"> <div class="publisher-side publisher-back">
<div class="publisher-img-container d-inline-flex align-items-center me-2 position-relative"> <div class="publisher-img-container d-inline-flex align-items-center me-2 position-relative">
<app-image <app-image
[imageUrl]="imageService.getPersonImage(nextPublisher!.id)" [imageUrl]="imageService.getPublisherImage(nextPublisher!.name)"
[classes]="'me-2'" [classes]="'me-2'"
[hideOnError]="true" [hideOnError]="true"
width="32px" width="32px"

View File

@ -1,18 +1,33 @@
//.publisher-flipper-container {
// perspective: 1000px;
//}
// //
//.publisher-img-container { //.publisher-img-container {
// transform-style: preserve-3d; // background-color: var(--card-bg-color);
// transition: transform 0.5s; // border-radius: 3px;
// padding: 2px 5px;
// font-size: 0.8rem;
// vertical-align: middle;
//
// div {
// min-height: 32px;
// line-height: 32px;
// }
//} //}
// Animation code
// jumpbar example
.publisher-wrapper { .publisher-wrapper {
perspective: 1000px; perspective: 1000px;
height: 32px; height: 32px;
background-color: var(--card-bg-color);
border-radius: 3px;
padding: 2px 5px;
font-size: 0.8rem;
vertical-align: middle;
div {
min-height: 32px;
line-height: 32px;
}
} }
.publisher-flipper { .publisher-flipper {
@ -20,7 +35,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
text-align: left; text-align: left;
transition: transform 0.6s; transition: transform 0.6s ease;
transform-style: preserve-3d; transform-style: preserve-3d;
} }
@ -33,7 +48,6 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
backface-visibility: hidden; backface-visibility: hidden;
transform-style: preserve-3d;
} }
.publisher-front { .publisher-front {

View File

@ -1,4 +1,14 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnDestroy, OnInit} from '@angular/core'; import {
AfterViewChecked,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import {ImageComponent} from "../../shared/image/image.component"; import {ImageComponent} from "../../shared/image/image.component";
import {FilterField} from "../../_models/metadata/v2/filter-field"; import {FilterField} from "../../_models/metadata/v2/filter-field";
import {Person} from "../../_models/metadata/person"; import {Person} from "../../_models/metadata/person";
@ -19,7 +29,7 @@ const ANIMATION_TIME = 3000;
styleUrl: './publisher-flipper.component.scss', styleUrl: './publisher-flipper.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class PublisherFlipperComponent implements OnInit, OnDestroy { export class PublisherFlipperComponent implements OnInit, OnDestroy, AfterViewInit, AfterViewChecked {
protected readonly imageService = inject(ImageService); protected readonly imageService = inject(ImageService);
private readonly filterUtilityService = inject(FilterUtilitiesService); private readonly filterUtilityService = inject(FilterUtilitiesService);
@ -36,14 +46,26 @@ export class PublisherFlipperComponent implements OnInit, OnDestroy {
isFlipped = false; isFlipped = false;
private intervalId: any; private intervalId: any;
ngOnInit() { ngOnInit() {
if (this.publishers.length > 0) { if (this.publishers.length > 0) {
this.currentPublisher = this.publishers[0]; this.currentPublisher = this.publishers[0];
this.nextPublisher = this.publishers[1] || this.publishers[0]; this.nextPublisher = this.publishers[1] || this.publishers[0];
if (this.publishers.length > 1) { }
this.startFlipping(); }
}
ngAfterViewInit() {
if (this.publishers.length > 1) {
this.startFlipping(); // Start flipping cycle once the view is initialized
}
}
ngAfterViewChecked() {
// This lifecycle hook will be called after Angular performs change detection in each cycle
if (this.isFlipped) {
// Only update publishers after the flip is complete
this.currentIndex = (this.currentIndex + 1) % this.publishers.length;
this.currentPublisher = this.publishers[this.currentIndex];
this.nextPublisher = this.publishers[(this.currentIndex + 1) % this.publishers.length];
} }
} }
@ -55,22 +77,9 @@ export class PublisherFlipperComponent implements OnInit, OnDestroy {
private startFlipping() { private startFlipping() {
this.intervalId = setInterval(() => { this.intervalId = setInterval(() => {
// First flip // Toggle flip state, initiating the flip animation
this.isFlipped = true; this.isFlipped = !this.isFlipped;
this.cdRef.markForCheck(); this.cdRef.detectChanges(); // Explicitly detect changes to trigger re-render
// Update content after flip animation completes
setTimeout(() => {
// Update indices and content
this.currentIndex = (this.currentIndex + 1) % this.publishers.length;
this.currentPublisher = this.publishers[this.currentIndex];
this.nextPublisher = this.publishers[(this.currentIndex + 1) % this.publishers.length];
// Reset flip
this.isFlipped = false;
this.cdRef.markForCheck();
}, ANIMATION_TIME); // Full transition time to ensure flip completes
}, ANIMATION_TIME); }, ANIMATION_TIME);
} }

View File

@ -29,6 +29,7 @@ import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe"; import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {TranslocoLocaleModule} from "@jsverse/transloco-locale"; import {TranslocoLocaleModule} from "@jsverse/transloco-locale";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe"; import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {DefaultModalOptions} from "../../_models/default-modal-options";
@Component({ @Component({
selector: 'app-manage-scrobble-errors', selector: 'app-manage-scrobble-errors',
@ -111,7 +112,7 @@ export class ManageScrobbleErrorsComponent implements OnInit {
editSeries(seriesId: number) { editSeries(seriesId: number) {
this.seriesService.getSeries(seriesId).subscribe(series => { this.seriesService.getSeries(seriesId).subscribe(series => {
const modalRef = this.modalService.open(EditSeriesModalComponent, { size: 'xl' }); const modalRef = this.modalService.open(EditSeriesModalComponent, DefaultModalOptions);
modalRef.componentInstance.series = series; modalRef.componentInstance.series = series;
}); });
} }

View File

@ -20,6 +20,7 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component"; import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component";
import {ConfirmService} from "../../shared/confirm.service"; import {ConfirmService} from "../../shared/confirm.service";
import {SettingButtonComponent} from "../../settings/_components/setting-button/setting-button.component"; import {SettingButtonComponent} from "../../settings/_components/setting-button/setting-button.component";
import {DefaultModalOptions} from "../../_models/default-modal-options";
interface AdhocTask { interface AdhocTask {
name: string; name: string;
@ -128,7 +129,7 @@ export class ManageTasksSettingsComponent implements OnInit {
this.toastr.info(translate('toasts.no-updates')); this.toastr.info(translate('toasts.no-updates'));
return; return;
} }
const modalRef = this.modalService.open(UpdateNotificationModalComponent, { scrollable: true, size: 'lg' }); const modalRef = this.modalService.open(UpdateNotificationModalComponent, DefaultModalOptions);
modalRef.componentInstance.updateData = update; modalRef.componentInstance.updateData = update;
} }
}, },

View File

@ -22,6 +22,7 @@ import {makeBindingParser} from "@angular/compiler";
import {LoadingComponent} from "../../shared/loading/loading.component"; import {LoadingComponent} from "../../shared/loading/loading.component";
import {TimeAgoPipe} from "../../_pipes/time-ago.pipe"; import {TimeAgoPipe} from "../../_pipes/time-ago.pipe";
import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe"; import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
import {DefaultModalOptions} from "../../_models/default-modal-options";
@Component({ @Component({
selector: 'app-manage-users', selector: 'app-manage-users',
@ -87,7 +88,7 @@ export class ManageUsersComponent implements OnInit {
} }
openEditUser(member: Member) { openEditUser(member: Member) {
const modalRef = this.modalService.open(EditUserComponent, { scrollable: true, size: 'xl', fullscreen: 'md' }); const modalRef = this.modalService.open(EditUserComponent, DefaultModalOptions);
modalRef.componentInstance.member = member; modalRef.componentInstance.member = member;
modalRef.closed.subscribe(() => { modalRef.closed.subscribe(() => {
this.loadMembers(); this.loadMembers();
@ -107,7 +108,7 @@ export class ManageUsersComponent implements OnInit {
} }
inviteUser() { inviteUser() {
const modalRef = this.modalService.open(InviteUserComponent, {size: 'xl'}); const modalRef = this.modalService.open(InviteUserComponent, DefaultModalOptions);
modalRef.closed.subscribe((successful: boolean) => { modalRef.closed.subscribe((successful: boolean) => {
this.loadMembers(); this.loadMembers();
}); });
@ -133,7 +134,7 @@ export class ManageUsersComponent implements OnInit {
} }
updatePassword(member: Member) { updatePassword(member: Member) {
const modalRef = this.modalService.open(ResetPasswordModalComponent); const modalRef = this.modalService.open(ResetPasswordModalComponent, DefaultModalOptions);
modalRef.componentInstance.member = member; modalRef.componentInstance.member = member;
} }

View File

@ -43,6 +43,7 @@ import {User} from "../../_models/user";
import {ScrollService} from "../../_services/scroll.service"; import {ScrollService} from "../../_services/scroll.service";
import {ReaderService} from "../../_services/reader.service"; import {ReaderService} from "../../_services/reader.service";
import {SeriesFormatComponent} from "../../shared/series-format/series-format.component"; import {SeriesFormatComponent} from "../../shared/series-format/series-format.component";
import {DefaultModalOptions} from "../../_models/default-modal-options";
function deepClone(obj: any): any { function deepClone(obj: any): any {
if (obj === null || typeof obj !== 'object') { if (obj === null || typeof obj !== 'object') {
@ -278,7 +279,7 @@ export class SeriesCardComponent implements OnInit, OnChanges {
} }
openEditModal(data: Series) { openEditModal(data: Series) {
const modalRef = this.modalService.open(EditSeriesModalComponent, { size: 'lg' }); const modalRef = this.modalService.open(EditSeriesModalComponent, DefaultModalOptions);
modalRef.componentInstance.series = data; modalRef.componentInstance.series = data;
modalRef.closed.subscribe((closeResult: {success: boolean, series: Series, coverImageUpdate: boolean}) => { modalRef.closed.subscribe((closeResult: {success: boolean, series: Series, coverImageUpdate: boolean}) => {
if (closeResult.success) { if (closeResult.success) {

View File

@ -83,6 +83,7 @@ import {PublicationStatusPipe} from "../_pipes/publication-status.pipe";
import {DefaultDatePipe} from "../_pipes/default-date.pipe"; import {DefaultDatePipe} from "../_pipes/default-date.pipe";
import {MangaFormatPipe} from "../_pipes/manga-format.pipe"; import {MangaFormatPipe} from "../_pipes/manga-format.pipe";
import {CoverImageComponent} from "../_single-module/cover-image/cover-image.component"; import {CoverImageComponent} from "../_single-module/cover-image/cover-image.component";
import {DefaultModalOptions} from "../_models/default-modal-options";
enum TabID { enum TabID {
Related = 'related-tab', Related = 'related-tab',
@ -317,7 +318,7 @@ export class ChapterDetailComponent implements OnInit {
} }
openEditModal() { openEditModal() {
const ref = this.modalService.open(EditChapterModalComponent, { size: 'xl' }); const ref = this.modalService.open(EditChapterModalComponent, DefaultModalOptions);
ref.componentInstance.chapter = this.chapter; ref.componentInstance.chapter = this.chapter;
ref.componentInstance.libraryType = this.libraryType; ref.componentInstance.libraryType = this.libraryType;
ref.componentInstance.libraryId = this.libraryId; ref.componentInstance.libraryId = this.libraryId;

View File

@ -42,6 +42,7 @@ import {SeriesCardComponent} from "../../../cards/series-card/series-card.compon
import {ActionService} from "../../../_services/action.service"; import {ActionService} from "../../../_services/action.service";
import {KEY_CODES} from "../../../shared/_services/utility.service"; import {KEY_CODES} from "../../../shared/_services/utility.service";
import {WikiLink} from "../../../_models/wiki"; import {WikiLink} from "../../../_models/wiki";
import {DefaultModalOptions} from "../../../_models/default-modal-options";
@Component({ @Component({
@ -153,7 +154,7 @@ export class AllCollectionsComponent implements OnInit {
}); });
break; break;
case(Action.Edit): case(Action.Edit):
const modalRef = this.modalService.open(EditCollectionTagsComponent, { size: 'lg', scrollable: true }); const modalRef = this.modalService.open(EditCollectionTagsComponent, DefaultModalOptions);
modalRef.componentInstance.tag = collectionTag; modalRef.componentInstance.tag = collectionTag;
modalRef.closed.subscribe((results: {success: boolean, coverImageUpdated: boolean}) => { modalRef.closed.subscribe((results: {success: boolean, coverImageUpdated: boolean}) => {
if (results.success) { if (results.success) {

View File

@ -64,6 +64,7 @@ import {PromotedIconComponent} from "../../../shared/_components/promoted-icon/p
import { import {
SmartCollectionDrawerComponent SmartCollectionDrawerComponent
} from "../../../_single-module/smart-collection-drawer/smart-collection-drawer.component"; } from "../../../_single-module/smart-collection-drawer/smart-collection-drawer.component";
import {DefaultModalOptions} from "../../../_models/default-modal-options";
@Component({ @Component({
selector: 'app-collection-detail', selector: 'app-collection-detail',
@ -330,7 +331,7 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
} }
openEditCollectionTagModal(collectionTag: UserCollection) { openEditCollectionTagModal(collectionTag: UserCollection) {
const modalRef = this.modalService.open(EditCollectionTagsComponent, { size: 'lg', scrollable: true }); const modalRef = this.modalService.open(EditCollectionTagsComponent, DefaultModalOptions);
modalRef.componentInstance.tag = this.collectionTag; modalRef.componentInstance.tag = this.collectionTag;
modalRef.closed.subscribe((results: {success: boolean, coverImageUpdated: boolean}) => { modalRef.closed.subscribe((results: {success: boolean, coverImageUpdated: boolean}) => {
this.updateTag(this.collectionTag.id); this.updateTag(this.collectionTag.id);

View File

@ -26,6 +26,7 @@ import { SentenceCasePipe } from '../../../_pipes/sentence-case.pipe';
import { CircularLoaderComponent } from '../../../shared/circular-loader/circular-loader.component'; import { CircularLoaderComponent } from '../../../shared/circular-loader/circular-loader.component';
import { NgClass, NgStyle, AsyncPipe } from '@angular/common'; import { NgClass, NgStyle, AsyncPipe } from '@angular/common';
import {TranslocoDirective} from "@jsverse/transloco"; import {TranslocoDirective} from "@jsverse/transloco";
import {DefaultModalOptions} from "../../../_models/default-modal-options";
@Component({ @Component({
selector: 'app-nav-events-toggle', selector: 'app-nav-events-toggle',
@ -159,7 +160,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
handleUpdateAvailableClick(message: NotificationProgressEvent | UpdateVersionEvent) { handleUpdateAvailableClick(message: NotificationProgressEvent | UpdateVersionEvent) {
if (this.updateNotificationModalRef != null) { return; } if (this.updateNotificationModalRef != null) { return; }
this.updateNotificationModalRef = this.modalService.open(UpdateNotificationModalComponent, { scrollable: true, size: 'lg' }); this.updateNotificationModalRef = this.modalService.open(UpdateNotificationModalComponent, DefaultModalOptions);
if (message.hasOwnProperty('body')) { if (message.hasOwnProperty('body')) {
this.updateNotificationModalRef.componentInstance.updateData = (message as NotificationProgressEvent).body as UpdateVersionEvent; this.updateNotificationModalRef.componentInstance.updateData = (message as NotificationProgressEvent).body as UpdateVersionEvent;
} else { } else {

View File

@ -38,6 +38,7 @@ import {EditPersonModalComponent} from "./_modal/edit-person-modal/edit-person-m
import {translate, TranslocoDirective} from "@jsverse/transloco"; import {translate, TranslocoDirective} from "@jsverse/transloco";
import {ChapterCardComponent} from "../cards/chapter-card/chapter-card.component"; import {ChapterCardComponent} from "../cards/chapter-card/chapter-card.component";
import {ThemeService} from "../_services/theme.service"; import {ThemeService} from "../_services/theme.service";
import {DefaultModalOptions} from "../_models/default-modal-options";
@Component({ @Component({
selector: 'app-person-detail', selector: 'app-person-detail',
@ -109,7 +110,7 @@ export class PersonDetailComponent {
this.themeService.setColorScape(person.primaryColor || '', person.secondaryColor); this.themeService.setColorScape(person.primaryColor || '', person.secondaryColor);
// Fetch roles and process them // Fetch roles and process them
this.roles$ = this.personService.getRolesForPerson(this.personName).pipe( this.roles$ = this.personService.getRolesForPerson(this.person.id).pipe(
tap(roles => { tap(roles => {
this.roles = roles; this.roles = roles;
this.filter = this.createFilter(roles); this.filter = this.createFilter(roles);
@ -187,7 +188,7 @@ export class PersonDetailComponent {
handleAction(action: ActionItem<Person>, person: Person) { handleAction(action: ActionItem<Person>, person: Person) {
switch (action.action) { switch (action.action) {
case(Action.Edit): case(Action.Edit):
const ref = this.modalService.open(EditPersonModalComponent, {scrollable: true, size: 'lg', fullscreen: 'md'}); const ref = this.modalService.open(EditPersonModalComponent, DefaultModalOptions);
ref.componentInstance.person = this.person; ref.componentInstance.person = this.person;
ref.closed.subscribe(r => { ref.closed.subscribe(r => {

View File

@ -5,10 +5,9 @@
<app-image [imageUrl]="imageService.getPublisherImage(entity.publishers[0].name)" [classes]="'me-2'" [hideOnError]="true" width="32px" height="32px" <app-image [imageUrl]="imageService.getPublisherImage(entity.publishers[0].name)" [classes]="'me-2'" [hideOnError]="true" width="32px" height="32px"
aria-hidden="true"></app-image> aria-hidden="true"></app-image>
<div class="position-relative d-inline-block" (click)="openGeneric(FilterField.Publisher, entity.publishers[0].id)">{{entity.publishers[0].name}}</div> <div class="position-relative d-inline-block" (click)="openGeneric(FilterField.Publisher, entity.publishers[0].id)">{{entity.publishers[0].name}}</div>
<!-- <app-publisher-flipper [publishers]="entity.publishers"></app-publisher-flipper>-->
</div> </div>
} }
<!-- TODO: Figure out if I can implement this animation (ROBBIE)-->
<!-- <app-publisher-flipper [publishers]="entity.publishers"></app-publisher-flipper>-->
<span class="me-2"> <span class="me-2">
<app-age-rating-image [rating]="ageRating"></app-age-rating-image> <app-age-rating-image [rating]="ageRating"></app-age-rating-image>
</span> </span>
@ -17,7 +16,7 @@
<app-series-format [format]="mangaFormat" [useTitle]="false"></app-series-format> <app-series-format [format]="mangaFormat" [useTitle]="false"></app-series-format>
</span> </span>
@if (libraryType === LibraryType.Book || libraryType === LibraryType.LightNovel) { @if ((libraryType === LibraryType.Book || libraryType === LibraryType.LightNovel) && mangaFormat !== MangaFormat.PDF) {
<span class="word-count me-3">{{t('words-count', {num: readingTimeEntity.wordCount | compactNumber})}}</span> <span class="word-count me-3">{{t('words-count', {num: readingTimeEntity.wordCount | compactNumber})}}</span>
} @else { } @else {
<span class="word-count me-3">{{t('pages-count', {num: readingTimeEntity.pages | compactNumber})}}</span> <span class="word-count me-3">{{t('pages-count', {num: readingTimeEntity.pages | compactNumber})}}</span>

View File

@ -46,6 +46,8 @@ export class MetadataDetailRowComponent {
private readonly filterUtilityService = inject(FilterUtilitiesService); private readonly filterUtilityService = inject(FilterUtilitiesService);
protected readonly LibraryType = LibraryType; protected readonly LibraryType = LibraryType;
protected readonly FilterField = FilterField;
protected readonly MangaFormat = MangaFormat;
@Input({required: true}) entity!: IHasCast; @Input({required: true}) entity!: IHasCast;
@Input({required: true}) readingTimeEntity!: IHasReadingTime; @Input({required: true}) readingTimeEntity!: IHasReadingTime;
@ -59,7 +61,4 @@ export class MetadataDetailRowComponent {
if (queryParamName === FilterField.None) return; if (queryParamName === FilterField.None) return;
this.filterUtilityService.applyFilter(['all-series'], queryParamName, FilterComparison.Equal, `${filter}`).subscribe(); this.filterUtilityService.applyFilter(['all-series'], queryParamName, FilterComparison.Equal, `${filter}`).subscribe();
} }
protected readonly FilterField = FilterField;
} }

View File

@ -145,6 +145,7 @@ import {UserCollection} from "../../../_models/collection-tag";
import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component"; import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component";
import {MangaFormatPipe} from "../../../_pipes/manga-format.pipe"; import {MangaFormatPipe} from "../../../_pipes/manga-format.pipe";
import {CoverImageComponent} from "../../../_single-module/cover-image/cover-image.component"; import {CoverImageComponent} from "../../../_single-module/cover-image/cover-image.component";
import {DefaultModalOptions} from "../../../_models/default-modal-options";
enum TabID { enum TabID {
@ -1041,7 +1042,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
} }
openEditChapter(chapter: Chapter) { openEditChapter(chapter: Chapter) {
const ref = this.modalService.open(EditChapterModalComponent, { size: 'xl' }); const ref = this.modalService.open(EditChapterModalComponent, DefaultModalOptions);
ref.componentInstance.chapter = chapter; ref.componentInstance.chapter = chapter;
ref.componentInstance.libraryType = this.libraryType; ref.componentInstance.libraryType = this.libraryType;
ref.componentInstance.seriesId = this.series?.id; ref.componentInstance.seriesId = this.series?.id;
@ -1055,7 +1056,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
} }
openEditVolume(volume: Volume) { openEditVolume(volume: Volume) {
const ref = this.modalService.open(EditVolumeModalComponent, { size: 'xl' }); const ref = this.modalService.open(EditVolumeModalComponent, DefaultModalOptions);
ref.componentInstance.volume = volume; ref.componentInstance.volume = volume;
ref.componentInstance.libraryType = this.libraryType; ref.componentInstance.libraryType = this.libraryType;
ref.componentInstance.seriesId = this.series?.id; ref.componentInstance.seriesId = this.series?.id;
@ -1069,7 +1070,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
} }
openEditSeriesModal() { openEditSeriesModal() {
const modalRef = this.modalService.open(EditSeriesModalComponent, { size: 'xl' }); const modalRef = this.modalService.open(EditSeriesModalComponent, DefaultModalOptions);
modalRef.componentInstance.series = this.series; modalRef.componentInstance.series = this.series;
modalRef.closed.subscribe((closeResult: EditSeriesModalCloseResult) => { modalRef.closed.subscribe((closeResult: EditSeriesModalCloseResult) => {
if (closeResult.success) { if (closeResult.success) {
@ -1088,7 +1089,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
openReviewModal() { openReviewModal() {
const userReview = this.getUserReview(); const userReview = this.getUserReview();
const modalRef = this.modalService.open(ReviewSeriesModalComponent, { scrollable: true, size: 'lg' }); const modalRef = this.modalService.open(ReviewSeriesModalComponent, DefaultModalOptions);
modalRef.componentInstance.series = this.series; modalRef.componentInstance.series = this.series;
if (userReview.length > 0) { if (userReview.length > 0) {
modalRef.componentInstance.review = userReview[0]; modalRef.componentInstance.review = userReview[0];

View File

@ -2,13 +2,16 @@
<div class="tagbadge cursor clickable"> <div class="tagbadge cursor clickable">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
@if (HasCoverImage) { @if (HasCoverImage) {
<app-image height="24px" width="24px" [styles]="{'object-fit': 'contain'}" <div class="mx-auto">
classes="align-self-center text-center mb-2" <app-image height="24px" width="24px" [styles]="{'background': 'none', 'max-height': '48px', 'height': '48px', 'width': '48px', 'border-radius': '50%'}"
[imageUrl]="ImageUrl" [imageUrl]="ImageUrl"
[errorImage]="imageService.noPersonImage"> [errorImage]="imageService.noPersonImage">
</app-image> </app-image>
</div>
} @else { } @else {
<i class="fas fa-user mx-auto" aria-hidden="true"></i> <div style="background: none; max-height: 48px; height: 48px; width: 48px; border-radius: 50%" class="mx-auto">
<i class="fas fa-user" aria-hidden="true"></i>
</div>
} }

View File

@ -7,11 +7,11 @@
font-size: .8rem; font-size: .8rem;
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
width: 100px; width: 120px;
word-break: break-word; word-break: break-word;
i { i {
font-size: 2rem; font-size: 2.96rem;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
} }

View File

@ -19,6 +19,7 @@ import {ScrobbleEventTypePipe} from "../../_pipes/scrobble-event-type.pipe";
import {SortableHeader} from "../../_single-module/table/_directives/sortable-header.directive"; import {SortableHeader} from "../../_single-module/table/_directives/sortable-header.directive";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe"; import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {EditDeviceModalComponent} from "../_modals/edit-device-modal/edit-device-modal.component"; import {EditDeviceModalComponent} from "../_modals/edit-device-modal/edit-device-modal.component";
import {DefaultModalOptions} from "../../_models/default-modal-options";
@Component({ @Component({
selector: 'app-manage-devices', selector: 'app-manage-devices',
@ -71,7 +72,7 @@ export class ManageDevicesComponent implements OnInit {
} }
addDevice() { addDevice() {
const ref = this.modalService.open(EditDeviceModalComponent, { scrollable: true, size: 'xl', fullscreen: 'md' }); const ref = this.modalService.open(EditDeviceModalComponent, DefaultModalOptions);
ref.componentInstance.device = null; ref.componentInstance.device = null;
ref.closed.subscribe((result: Device | null) => { ref.closed.subscribe((result: Device | null) => {
@ -82,7 +83,7 @@ export class ManageDevicesComponent implements OnInit {
} }
editDevice(device: Device) { editDevice(device: Device) {
const ref = this.modalService.open(EditDeviceModalComponent, { scrollable: true, size: 'xl', fullscreen: 'md' }); const ref = this.modalService.open(EditDeviceModalComponent, DefaultModalOptions);
ref.componentInstance.device = device; ref.componentInstance.device = device;
ref.closed.subscribe((result: Device | null) => { ref.closed.subscribe((result: Device | null) => {

View File

@ -31,6 +31,7 @@ import {Select2Module} from "ng-select2-component";
import {LoadingComponent} from "../../shared/loading/loading.component"; import {LoadingComponent} from "../../shared/loading/loading.component";
import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
import {PreviewImageModalComponent} from "../../shared/_components/carousel-modal/preview-image-modal.component"; import {PreviewImageModalComponent} from "../../shared/_components/carousel-modal/preview-image-modal.component";
import {DefaultModalOptions} from "../../_models/default-modal-options";
interface ThemeContainer { interface ThemeContainer {
downloadable?: DownloadableSiteTheme; downloadable?: DownloadableSiteTheme;
@ -191,7 +192,7 @@ export class ThemeManagerComponent {
previewImage(imgUrl: string) { previewImage(imgUrl: string) {
if (imgUrl === '') return; if (imgUrl === '') return;
const ref = this.modalService.open(PreviewImageModalComponent, {size: 'xl', fullscreen: 'lg'}); const ref = this.modalService.open(PreviewImageModalComponent, DefaultModalOptions);
ref.componentInstance.title = this.selectedTheme!.name; ref.componentInstance.title = this.selectedTheme!.name;
ref.componentInstance.image = imgUrl; ref.componentInstance.image = imgUrl;
} }

View File

@ -88,6 +88,7 @@ import {BulkOperationsComponent} from "../cards/bulk-operations/bulk-operations.
import {DefaultDatePipe} from "../_pipes/default-date.pipe"; import {DefaultDatePipe} from "../_pipes/default-date.pipe";
import {MangaFormatPipe} from "../_pipes/manga-format.pipe"; import {MangaFormatPipe} from "../_pipes/manga-format.pipe";
import {CoverImageComponent} from "../_single-module/cover-image/cover-image.component"; import {CoverImageComponent} from "../_single-module/cover-image/cover-image.component";
import {DefaultModalOptions} from "../_models/default-modal-options";
enum TabID { enum TabID {
@ -527,7 +528,7 @@ export class VolumeDetailComponent implements OnInit {
} }
openEditModal() { openEditModal() {
const ref = this.modalService.open(EditVolumeModalComponent, { size: 'xl' }); const ref = this.modalService.open(EditVolumeModalComponent, DefaultModalOptions);
ref.componentInstance.volume = this.volume; ref.componentInstance.volume = this.volume;
ref.componentInstance.libraryType = this.libraryType; ref.componentInstance.libraryType = this.libraryType;
ref.componentInstance.libraryId = this.libraryId; ref.componentInstance.libraryId = this.libraryId;
@ -537,7 +538,7 @@ export class VolumeDetailComponent implements OnInit {
} }
openEditChapterModal(chapter: Chapter) { openEditChapterModal(chapter: Chapter) {
const ref = this.modalService.open(EditChapterModalComponent, { size: 'xl' }); const ref = this.modalService.open(EditChapterModalComponent, DefaultModalOptions);
ref.componentInstance.chapter = chapter; ref.componentInstance.chapter = chapter;
ref.componentInstance.libraryType = this.libraryType; ref.componentInstance.libraryType = this.libraryType;
ref.componentInstance.libraryId = this.libraryId; ref.componentInstance.libraryId = this.libraryId;