Skip to content

Commit

Permalink
UX Overhaul Part 1 (#3047)
Browse files Browse the repository at this point in the history
Co-authored-by: Joseph Milazzo <[email protected]>
  • Loading branch information
therobbiedavis and majora2007 authored Aug 9, 2024
1 parent 5934d51 commit ff79710
Show file tree
Hide file tree
Showing 324 changed files with 11,604 additions and 4,613 deletions.
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,6 @@ UI/Web/.angular/
BenchmarkDotNet.Artifacts


API.Tests/Services/Test Data/ImageService/Covers/*_output*
API.Tests/Services/Test Data/ImageService/Covers/*_baseline*
API.Tests/Services/Test Data/ImageService/Covers/index.html
API.Tests/Services/Test Data/ImageService/**/*_output*
API.Tests/Services/Test Data/ImageService/**/*_baseline*
API.Tests/Services/Test Data/ImageService/**/*.html
4 changes: 2 additions & 2 deletions API.Benchmark/API.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.12" />
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.14.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
</ItemGroup>

Expand Down
10 changes: 5 additions & 5 deletions API.Tests/API.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="21.0.22" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="21.0.22" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="21.0.29" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="21.0.29" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
102 changes: 101 additions & 1 deletion API.Tests/Services/ImageServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
using System.IO;
using System.Drawing;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text;
using API.Entities.Enums;
using API.Services;
using EasyCaching.Core;
using Microsoft.Extensions.Logging;
using NetVips;
using NSubstitute;
using Xunit;
using Image = NetVips.Image;

Expand All @@ -12,6 +17,7 @@ namespace API.Tests.Services;
public class ImageServiceTests
{
private readonly string _testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ImageService/Covers");
private readonly string _testDirectoryColorScapes = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ImageService/ColorScapes");
private const string OutputPattern = "_output";
private const string BaselinePattern = "_baseline";

Expand Down Expand Up @@ -121,4 +127,98 @@ private void GenerateHtmlFile()
File.WriteAllText(Path.Combine(_testDirectory, "index.html"), htmlBuilder.ToString());
}


[Fact]
public void TestColorScapes()
{
// Step 1: Delete any images that have _output in the name
var outputFiles = Directory.GetFiles(_testDirectoryColorScapes, "*_output.*");
foreach (var file in outputFiles)
{
File.Delete(file);
}

// Step 2: Scan the _testDirectory for images
var imageFiles = Directory.GetFiles(_testDirectoryColorScapes, "*.*")
.Where(file => !file.EndsWith("html"))
.Where(file => !file.Contains(OutputPattern) && !file.Contains(BaselinePattern))
.ToList();

// Step 3: Process each image
foreach (var imagePath in imageFiles)
{
var fileName = Path.GetFileNameWithoutExtension(imagePath);
var colors = ImageService.CalculateColorScape(imagePath);

// Generate primary color image
GenerateColorImage(colors.Primary, Path.Combine(_testDirectoryColorScapes, $"{fileName}_primary_output.png"));

// Generate secondary color image
GenerateColorImage(colors.Secondary, Path.Combine(_testDirectoryColorScapes, $"{fileName}_secondary_output.png"));
}

// Step 4: Generate HTML file
GenerateHtmlFileForColorScape();

}

private static void GenerateColorImage(string hexColor, string outputPath)
{
var color = ImageService.HexToRgb(hexColor);
using var colorImage = Image.Black(200, 100);
using var output = colorImage + new[] { color.R / 255.0, color.G / 255.0, color.B / 255.0 };
output.WriteToFile(outputPath);
}

private void GenerateHtmlFileForColorScape()
{
var imageFiles = Directory.GetFiles(_testDirectoryColorScapes, "*.*")
.Where(file => !file.EndsWith("html"))
.Where(file => !file.Contains(OutputPattern) && !file.Contains(BaselinePattern))
.ToList();

var htmlBuilder = new StringBuilder();
htmlBuilder.AppendLine("<!DOCTYPE html>");
htmlBuilder.AppendLine("<html lang=\"en\">");
htmlBuilder.AppendLine("<head>");
htmlBuilder.AppendLine("<meta charset=\"UTF-8\">");
htmlBuilder.AppendLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
htmlBuilder.AppendLine("<title>Color Scape Comparison</title>");
htmlBuilder.AppendLine("<style>");
htmlBuilder.AppendLine("body { font-family: Arial, sans-serif; }");
htmlBuilder.AppendLine(".container { display: flex; flex-wrap: wrap; }");
htmlBuilder.AppendLine(".image-row { display: flex; align-items: center; margin-bottom: 20px; width: 100% }");
htmlBuilder.AppendLine(".image-row img { margin-right: 10px; max-width: 200px; height: auto; }");
htmlBuilder.AppendLine(".color-square { width: 100px; height: 100px; margin-right: 10px; }");
htmlBuilder.AppendLine("</style>");
htmlBuilder.AppendLine("</head>");
htmlBuilder.AppendLine("<body>");
htmlBuilder.AppendLine("<div class=\"container\">");

foreach (var imagePath in imageFiles)
{
var fileName = Path.GetFileNameWithoutExtension(imagePath);
var primaryPath = Path.Combine(_testDirectoryColorScapes, $"{fileName}_primary_output.png");
var secondaryPath = Path.Combine(_testDirectoryColorScapes, $"{fileName}_secondary_output.png");

htmlBuilder.AppendLine("<div class=\"image-row\">");
htmlBuilder.AppendLine($"<p>{fileName}</p>");
htmlBuilder.AppendLine($"<img src=\"./{Path.GetFileName(imagePath)}\" alt=\"{fileName}\">");
if (File.Exists(primaryPath))
{
htmlBuilder.AppendLine($"<img class=\"color-square\" src=\"./{Path.GetFileName(primaryPath)}\" alt=\"{fileName} primary color\">");
}
if (File.Exists(secondaryPath))
{
htmlBuilder.AppendLine($"<img class=\"color-square\" src=\"./{Path.GetFileName(secondaryPath)}\" alt=\"{fileName} secondary color\">");
}
htmlBuilder.AppendLine("</div>");
}

htmlBuilder.AppendLine("</div>");
htmlBuilder.AppendLine("</body>");
htmlBuilder.AppendLine("</html>");

File.WriteAllText(Path.Combine(_testDirectoryColorScapes, "colorscape_index.html"), htmlBuilder.ToString());
}
}
4 changes: 3 additions & 1 deletion API.Tests/Services/ReadingListServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ public ReadingListServiceTests()
var mapper = config.CreateMapper();
_unitOfWork = new UnitOfWork(_context, mapper, null!);

_readingListService = new ReadingListService(_unitOfWork, Substitute.For<ILogger<ReadingListService>>(), Substitute.For<IEventHub>());
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem());
_readingListService = new ReadingListService(_unitOfWork, Substitute.For<ILogger<ReadingListService>>(),
Substitute.For<IEventHub>(), Substitute.For<IImageService>(), ds);

_readerService = new ReaderService(_unitOfWork, Substitute.For<ILogger<ReaderService>>(),
Substitute.For<IEventHub>(), Substitute.For<IImageService>(),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 16 additions & 16 deletions API/API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@

<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="MailKit" Version="4.7.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
<PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand All @@ -70,41 +70,41 @@
<PackageReference Include="Hangfire.InMemory" Version="0.10.3" />
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.2" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.62" />
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
<PackageReference Include="Nager.ArticleNumber" Version="1.0.7" />
<PackageReference Include="NetVips" Version="2.4.1" />
<PackageReference Include="NetVips.Native" Version="8.15.2" />
<PackageReference Include="NReco.Logging.File" Version="1.2.1" />
<PackageReference Include="Serilog" Version="4.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.2" />
<PackageReference Include="Serilog.Sinks.AspNetCore.SignalR" Version="0.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
<PackageReference Include="SharpCompress" Version="0.37.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.28.0.94264">
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.31.0.96804">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.2" />
<PackageReference Include="System.IO.Abstractions" Version="21.0.22" />
<PackageReference Include="System.Drawing.Common" Version="8.0.6" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
<PackageReference Include="System.IO.Abstractions" Version="21.0.29" />
<PackageReference Include="System.Drawing.Common" Version="8.0.7" />
<PackageReference Include="VersOne.Epub" Version="3.3.2" />
</ItemGroup>

Expand Down
64 changes: 64 additions & 0 deletions API/Controllers/ColorScapeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Theme;
using API.Entities.Interfaces;
using API.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers;

[Authorize]
public class ColorScapeController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;

public ColorScapeController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}

/// <summary>
/// Returns the color scape for a series
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("series")]
public async Task<ActionResult<ColorScapeDto>> GetColorScapeForSeries(int id)
{
var entity = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(id, User.GetUserId());
return GetColorSpaceDto(entity);
}

/// <summary>
/// Returns the color scape for a volume
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("volume")]
public async Task<ActionResult<ColorScapeDto>> GetColorScapeForVolume(int id)
{
var entity = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(id, User.GetUserId());
return GetColorSpaceDto(entity);
}

/// <summary>
/// Returns the color scape for a chapter
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("chapter")]
public async Task<ActionResult<ColorScapeDto>> GetColorScapeForChapter(int id)
{
var entity = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(id);
return GetColorSpaceDto(entity);
}



private ActionResult<ColorScapeDto> GetColorSpaceDto(IHasCoverImage entity)
{
if (entity == null) return Ok(ColorScapeDto.Empty);
return Ok(new ColorScapeDto(entity.PrimaryColor, entity.SecondaryColor));
}
}
28 changes: 19 additions & 9 deletions API/Controllers/DeviceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using API.Extensions;
using API.Services;
using API.SignalR;
using AutoMapper;
using Kavita.Common;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -24,20 +25,27 @@ public class DeviceController : BaseApiController
private readonly IEmailService _emailService;
private readonly IEventHub _eventHub;
private readonly ILocalizationService _localizationService;
private readonly IMapper _mapper;

public DeviceController(IUnitOfWork unitOfWork, IDeviceService deviceService,
IEmailService emailService, IEventHub eventHub, ILocalizationService localizationService)
IEmailService emailService, IEventHub eventHub, ILocalizationService localizationService, IMapper mapper)
{
_unitOfWork = unitOfWork;
_deviceService = deviceService;
_emailService = emailService;
_eventHub = eventHub;
_localizationService = localizationService;
_mapper = mapper;
}


/// <summary>
/// Creates a new Device
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("create")]
public async Task<ActionResult> CreateOrUpdateDevice(CreateDeviceDto dto)
public async Task<ActionResult<DeviceDto>> CreateOrUpdateDevice(CreateDeviceDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
if (user == null) return Unauthorized();
Expand All @@ -46,28 +54,30 @@ public async Task<ActionResult> CreateOrUpdateDevice(CreateDeviceDto dto)
var device = await _deviceService.Create(dto, user);
if (device == null)
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-device-create"));

return Ok(_mapper.Map<DeviceDto>(device));
}
catch (KavitaException ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}




return Ok();
}

/// <summary>
/// Updates an existing Device
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update")]
public async Task<ActionResult> UpdateDevice(UpdateDeviceDto dto)
public async Task<ActionResult<DeviceDto>> UpdateDevice(UpdateDeviceDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
if (user == null) return Unauthorized();
var device = await _deviceService.Update(dto, user);

if (device == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-device-update"));

return Ok();
return Ok(_mapper.Map<DeviceDto>(device));
}

/// <summary>
Expand Down
Loading

0 comments on commit ff79710

Please sign in to comment.