First iteration of the UI

- Migrate current preferences over
- Set defaults in db
This commit is contained in:
Amelia 2025-05-18 01:25:24 +02:00
parent 5741a92bb2
commit 5656fb2148
26 changed files with 1246 additions and 728 deletions

View file

@ -257,6 +257,19 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
builder.Entity<MetadataSettings>()
.Property(b => b.EnableCoverImage)
.HasDefaultValue(true);
builder.Entity<AppUserReadingProfile>()
.Property(b => b.BookThemeName)
.HasDefaultValue("Dark");
builder.Entity<AppUserReadingProfile>()
.Property(b => b.BackgroundColor)
.HasDefaultValue("#000000");
builder.Entity<AppUserReadingProfile>()
.Property(b => b.BookReaderWritingStyle)
.HasDefaultValue(WritingStyle.Horizontal);
builder.Entity<AppUserReadingProfile>()
.Property(b => b.AllowAutomaticWebtoonReaderDetection)
.HasDefaultValue(true);
}
#nullable enable

View file

@ -1,13 +1,16 @@
using System;
using System.Threading.Tasks;
using API.Entities;
using API.Entities.History;
using API.Extensions;
using API.Helpers.Builders;
using Kavita.Common.EnvironmentInfo;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data.ManualMigrations.v0._8._7;
namespace API.Data.ManualMigrations;
public class ManualMigrateReadingProfiles
public static class ManualMigrateReadingProfiles
{
public static async Task Migrate(DataContext context, ILogger<Program> logger)
{
@ -18,62 +21,55 @@ public class ManualMigrateReadingProfiles
logger.LogCritical("Running ManualMigrateReadingProfiles migration - Please be patient, this may take some time. This is not an error");
await context.Database.ExecuteSqlRawAsync(@"
INSERT INTO AppUserReadingProfiles (
AppUserId,
ReadingDirection,
ScalingOption,
PageSplitOption,
ReaderMode,
AutoCloseMenu,
ShowScreenHints,
EmulateBook,
LayoutMode,
BackgroundColor,
SwipeToPaginate,
AllowAutomaticWebtoonReaderDetection,
BookReaderMargin,
BookReaderLineSpacing,
BookReaderFontSize,
BookReaderFontFamily,
BookReaderTapToPaginate,
BookReaderReadingDirection,
BookReaderWritingStyle,
BookThemeName,
BookReaderLayoutMode,
BookReaderImmersiveMode,
PdfTheme,
PdfScrollMode,
PdfSpreadMode
)
SELECT
AppUserId,
ReadingDirection,
ScalingOption,
PageSplitOption,
ReaderMode,
AutoCloseMenu,
ShowScreenHints,
EmulateBook,
LayoutMode,
BackgroundColor,
SwipeToPaginate,
AllowAutomaticWebtoonReaderDetection,
BookReaderMargin,
BookReaderLineSpacing,
BookReaderFontSize,
BookReaderFontFamily,
BookReaderTapToPaginate,
BookReaderReadingDirection,
BookReaderWritingStyle,
BookThemeName,
BookReaderLayoutMode,
BookReaderImmersiveMode,
PdfTheme,
PdfScrollMode,
PdfSpreadMode
FROM AppUserPreferences
");
var users = await context.AppUser
.Include(u => u.UserPreferences)
.Include(u => u.UserPreferences.ReadingProfiles)
.ToListAsync();
foreach (var user in users)
{
var readingProfile = new AppUserReadingProfile
{
Name = "Default",
NormalizedName = "Default".ToNormalized(),
BackgroundColor = user.UserPreferences.BackgroundColor,
EmulateBook = user.UserPreferences.EmulateBook,
User = user,
PdfTheme = user.UserPreferences.PdfTheme,
ReaderMode = user.UserPreferences.ReaderMode,
ReadingDirection = user.UserPreferences.ReadingDirection,
ScalingOption = user.UserPreferences.ScalingOption,
LayoutMode = user.UserPreferences.LayoutMode,
WidthOverride = null,
UserId = user.Id,
AutoCloseMenu = user.UserPreferences.AutoCloseMenu,
BookReaderMargin = user.UserPreferences.BookReaderMargin,
PageSplitOption = user.UserPreferences.PageSplitOption,
BookThemeName = user.UserPreferences.BookThemeName,
PdfSpreadMode = user.UserPreferences.PdfSpreadMode,
PdfScrollMode = user.UserPreferences.PdfScrollMode,
SwipeToPaginate = user.UserPreferences.SwipeToPaginate,
BookReaderFontFamily = user.UserPreferences.BookReaderFontFamily,
BookReaderFontSize = user.UserPreferences.BookReaderFontSize,
BookReaderImmersiveMode = user.UserPreferences.BookReaderImmersiveMode,
BookReaderLayoutMode = user.UserPreferences.BookReaderLayoutMode,
BookReaderLineSpacing = user.UserPreferences.BookReaderLineSpacing,
BookReaderReadingDirection = user.UserPreferences.BookReaderReadingDirection,
BookReaderWritingStyle = user.UserPreferences.BookReaderWritingStyle,
AllowAutomaticWebtoonReaderDetection = user.UserPreferences.AllowAutomaticWebtoonReaderDetection,
BookReaderTapToPaginate = user.UserPreferences.BookReaderTapToPaginate,
ShowScreenHints = user.UserPreferences.ShowScreenHints,
};
user.UserPreferences.ReadingProfiles.Add(readingProfile);
}
await context.SaveChangesAsync();
foreach (var user in users)
{
user.UserPreferences.DefaultReadingProfileId =
(await context.AppUserReadingProfile
.FirstAsync(rp => rp.UserId == user.Id)).Id;
}
context.ManualMigrationHistory.Add(new ManualMigrationHistory
{

View file

@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace API.Data.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250515215234_AppUserReadingProfiles")]
[Migration("20250517195000_AppUserReadingProfiles")]
partial class AppUserReadingProfiles
{
/// <inheritdoc />
@ -622,7 +622,9 @@ namespace API.Data.Migrations
.HasColumnType("INTEGER");
b.Property<bool>("AllowAutomaticWebtoonReaderDetection")
.HasColumnType("INTEGER");
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<int?>("AppUserPreferencesId")
.HasColumnType("INTEGER");
@ -631,7 +633,9 @@ namespace API.Data.Migrations
.HasColumnType("INTEGER");
b.Property<string>("BackgroundColor")
.HasColumnType("TEXT");
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("#000000");
b.Property<string>("BookReaderFontFamily")
.HasColumnType("TEXT");
@ -658,10 +662,14 @@ namespace API.Data.Migrations
.HasColumnType("INTEGER");
b.Property<int>("BookReaderWritingStyle")
.HasColumnType("INTEGER");
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<string>("BookThemeName")
.HasColumnType("TEXT");
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("Dark");
b.Property<bool>("EmulateBook")
.HasColumnType("INTEGER");

View file

@ -34,9 +34,9 @@ namespace API.Data.Migrations
ShowScreenHints = table.Column<bool>(type: "INTEGER", nullable: false),
EmulateBook = table.Column<bool>(type: "INTEGER", nullable: false),
LayoutMode = table.Column<int>(type: "INTEGER", nullable: false),
BackgroundColor = table.Column<string>(type: "TEXT", nullable: true),
BackgroundColor = table.Column<string>(type: "TEXT", nullable: true, defaultValue: "#000000"),
SwipeToPaginate = table.Column<bool>(type: "INTEGER", nullable: false),
AllowAutomaticWebtoonReaderDetection = table.Column<bool>(type: "INTEGER", nullable: false),
AllowAutomaticWebtoonReaderDetection = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true),
WidthOverride = table.Column<int>(type: "INTEGER", nullable: true),
BookReaderMargin = table.Column<int>(type: "INTEGER", nullable: false),
BookReaderLineSpacing = table.Column<int>(type: "INTEGER", nullable: false),
@ -44,8 +44,8 @@ namespace API.Data.Migrations
BookReaderFontFamily = table.Column<string>(type: "TEXT", nullable: true),
BookReaderTapToPaginate = table.Column<bool>(type: "INTEGER", nullable: false),
BookReaderReadingDirection = table.Column<int>(type: "INTEGER", nullable: false),
BookReaderWritingStyle = table.Column<int>(type: "INTEGER", nullable: false),
BookThemeName = table.Column<string>(type: "TEXT", nullable: true),
BookReaderWritingStyle = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0),
BookThemeName = table.Column<string>(type: "TEXT", nullable: true, defaultValue: "Dark"),
BookReaderLayoutMode = table.Column<int>(type: "INTEGER", nullable: false),
BookReaderImmersiveMode = table.Column<bool>(type: "INTEGER", nullable: false),
PdfTheme = table.Column<int>(type: "INTEGER", nullable: false),

View file

@ -619,7 +619,9 @@ namespace API.Data.Migrations
.HasColumnType("INTEGER");
b.Property<bool>("AllowAutomaticWebtoonReaderDetection")
.HasColumnType("INTEGER");
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<int?>("AppUserPreferencesId")
.HasColumnType("INTEGER");
@ -628,7 +630,9 @@ namespace API.Data.Migrations
.HasColumnType("INTEGER");
b.Property<string>("BackgroundColor")
.HasColumnType("TEXT");
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("#000000");
b.Property<string>("BookReaderFontFamily")
.HasColumnType("TEXT");
@ -655,10 +659,14 @@ namespace API.Data.Migrations
.HasColumnType("INTEGER");
b.Property<int>("BookReaderWritingStyle")
.HasColumnType("INTEGER");
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<string>("BookThemeName")
.HasColumnType("TEXT");
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("Dark");
b.Property<bool>("EmulateBook")
.HasColumnType("INTEGER");

View file

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using API.Extensions;
using API.Extensions.QueryExtensions;
using AutoMapper;
using AutoMapper.QueryableExtensions;
@ -22,13 +23,14 @@ public enum ReadingProfileIncludes
public interface IAppUserReadingProfileRepository
{
Task<IList<AppUserReadingProfile>> GetProfilesForUser(int userId);
Task<AppUserReadingProfile?> GetProfileForSeries(int userId, int seriesId);
Task<IList<AppUserReadingProfile>> GetProfilesForUser(int userId, bool nonImplicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
Task<AppUserReadingProfile?> GetProfileForSeries(int userId, int seriesId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
Task<UserReadingProfileDto?> GetProfileDtoForSeries(int userId, int seriesId);
Task<AppUserReadingProfile?> GetProfileForLibrary(int userId, int libraryId);
Task<AppUserReadingProfile?> GetProfileForLibrary(int userId, int libraryId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
Task<UserReadingProfileDto?> GetProfileDtoForLibrary(int userId, int libraryId);
Task<AppUserReadingProfile?> GetProfile(int profileId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
Task<UserReadingProfileDto?> GetProfileDto(int profileId);
Task<AppUserReadingProfile?> GetProfileByName(int userId, string name);
void Add(AppUserReadingProfile readingProfile);
void Update(AppUserReadingProfile readingProfile);
@ -38,17 +40,20 @@ public interface IAppUserReadingProfileRepository
public class AppUserReadingProfileRepository(DataContext context, IMapper mapper): IAppUserReadingProfileRepository
{
public async Task<IList<AppUserReadingProfile>> GetProfilesForUser(int userId)
public async Task<IList<AppUserReadingProfile>> GetProfilesForUser(int userId, bool nonImplicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None)
{
return await context.AppUserReadingProfile
.Where(rp => rp.UserId == userId)
.Where(rp => rp.UserId == userId && !(nonImplicitOnly && rp.Implicit))
.Includes(includes)
.ToListAsync();
}
public async Task<AppUserReadingProfile?> GetProfileForSeries(int userId, int seriesId)
public async Task<AppUserReadingProfile?> GetProfileForSeries(int userId, int seriesId, ReadingProfileIncludes includes = ReadingProfileIncludes.None)
{
return await context.AppUserReadingProfile
.Where(rp => rp.UserId == userId && rp.Series.Any(s => s.Id == seriesId))
.Includes(includes)
.OrderByDescending(rp => rp.Implicit) // Get implicit profiles first
.FirstOrDefaultAsync();
}
@ -56,14 +61,16 @@ public class AppUserReadingProfileRepository(DataContext context, IMapper mapper
{
return await context.AppUserReadingProfile
.Where(rp => rp.UserId == userId && rp.Series.Any(s => s.Id == seriesId))
.OrderByDescending(rp => rp.Implicit) // Get implicit profiles first
.ProjectTo<UserReadingProfileDto>(mapper.ConfigurationProvider)
.FirstOrDefaultAsync();
}
public async Task<AppUserReadingProfile?> GetProfileForLibrary(int userId, int libraryId)
public async Task<AppUserReadingProfile?> GetProfileForLibrary(int userId, int libraryId, ReadingProfileIncludes includes = ReadingProfileIncludes.None)
{
return await context.AppUserReadingProfile
.Where(rp => rp.UserId == userId && rp.Libraries.Any(s => s.Id == libraryId))
.Includes(includes)
.FirstOrDefaultAsync();
}
@ -90,6 +97,15 @@ public class AppUserReadingProfileRepository(DataContext context, IMapper mapper
.FirstOrDefaultAsync();
}
public async Task<AppUserReadingProfile?> GetProfileByName(int userId, string name)
{
var normalizedName = name.ToNormalized();
return await context.AppUserReadingProfile
.Where(rp => rp.NormalizedName == normalizedName && rp.UserId == userId)
.FirstOrDefaultAsync();
}
public void Add(AppUserReadingProfile readingProfile)
{
context.AppUserReadingProfile.Add(readingProfile);