From 2c9a87bf3e80e9932d9c2e82306fdbab69d42eb7 Mon Sep 17 00:00:00 2001 From: ChiaraBm Date: Sun, 20 Jul 2025 23:27:51 +0200 Subject: [PATCH] Expanding theme tab to customization tab. Started improving theme selection. --- .../Database/CoreDataContext.cs | 13 +- .../Database/Entities/Theme.cs | 19 + .../20250720203346_AddedThemes.Designer.cs | 564 ++++++++++++++++++ .../Migrations/20250720203346_AddedThemes.cs | 41 ++ .../CoreDataContextModelSnapshot.cs | 171 ++++++ .../Controllers/Admin/Sys/ThemeController.cs | 127 +++- .../Admin/Users/UsersController.cs | 2 +- Moonlight.ApiServer/Mappers/ThemeMapper.cs | 14 + .../Models/ApplicationTheme.cs | 49 ++ .../Moonlight.ApiServer.csproj | 1 + Moonlight.Client/Moonlight.Client.csproj | 5 + .../UI/Components/ColorSelector.razor | 45 ++ Moonlight.Client/UI/Components/StatCard.razor | 2 +- .../UI/Partials/Design/ThemeSettings.razor | 259 -------- .../UI/Views/Admin/Sys/Theme.razor | 198 +++++- .../Admin/Sys/Theme/CreateThemeRequest.cs | 21 + .../Admin/Sys/Theme/UpdateThemeRequest.cs | 23 + .../Requests/Admin/Sys/UpdateThemeRequest.cs | 9 - .../Http/Responses/Admin/ThemeResponse.cs | 19 + Moonlight.Shared/Misc/ApplicationTheme.cs | 49 ++ 20 files changed, 1336 insertions(+), 295 deletions(-) create mode 100644 Moonlight.ApiServer/Database/Entities/Theme.cs create mode 100644 Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs create mode 100644 Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.cs create mode 100644 Moonlight.ApiServer/Mappers/ThemeMapper.cs create mode 100644 Moonlight.ApiServer/Models/ApplicationTheme.cs create mode 100644 Moonlight.Client/UI/Components/ColorSelector.razor delete mode 100644 Moonlight.Client/UI/Partials/Design/ThemeSettings.razor create mode 100644 Moonlight.Shared/Http/Requests/Admin/Sys/Theme/CreateThemeRequest.cs create mode 100644 Moonlight.Shared/Http/Requests/Admin/Sys/Theme/UpdateThemeRequest.cs delete mode 100644 Moonlight.Shared/Http/Requests/Admin/Sys/UpdateThemeRequest.cs create mode 100644 Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs create mode 100644 Moonlight.Shared/Misc/ApplicationTheme.cs diff --git a/Moonlight.ApiServer/Database/CoreDataContext.cs b/Moonlight.ApiServer/Database/CoreDataContext.cs index b9875047..447f716a 100644 --- a/Moonlight.ApiServer/Database/CoreDataContext.cs +++ b/Moonlight.ApiServer/Database/CoreDataContext.cs @@ -3,16 +3,18 @@ using Microsoft.EntityFrameworkCore; using MoonCore.Extended.SingleDb; using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Database.Entities; +using Moonlight.ApiServer.Models; namespace Moonlight.ApiServer.Database; public class CoreDataContext : DatabaseContext { public override string Prefix { get; } = "Core"; - + public DbSet Users { get; set; } public DbSet ApiKeys { get; set; } - + public DbSet Themes { get; set; } + public CoreDataContext(AppConfiguration configuration) { Options = new() @@ -29,5 +31,12 @@ public class CoreDataContext : DatabaseContext { base.OnModelCreating(modelBuilder); modelBuilder.OnHangfireModelCreating(); + + modelBuilder.Ignore(); + modelBuilder.Entity() + .OwnsOne(x => x.Content, builder => + { + builder.ToJson(); + }); } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Entities/Theme.cs b/Moonlight.ApiServer/Database/Entities/Theme.cs new file mode 100644 index 00000000..22f1ca04 --- /dev/null +++ b/Moonlight.ApiServer/Database/Entities/Theme.cs @@ -0,0 +1,19 @@ +using Moonlight.ApiServer.Models; + +namespace Moonlight.ApiServer.Database.Entities; + +public class Theme +{ + public int Id { get; set; } + + public bool IsEnabled { get; set; } + + public string Name { get; set; } + public string Author { get; set; } + public string Version { get; set; } + + public string? UpdateUrl { get; set; } + public string? DonateUrl { get; set; } + + public ApplicationTheme Content { get; set; } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs b/Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs new file mode 100644 index 00000000..0e2cf75f --- /dev/null +++ b/Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs @@ -0,0 +1,564 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.ApiServer.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Moonlight.ApiServer.Database.Migrations +{ + [DbContext(typeof(CoreDataContext))] + [Migration("20250720203346_AddedThemes")] + partial class AddedThemes + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Value") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ExpireAt"); + + b.HasIndex("Key", "Value"); + + b.ToTable("HangfireCounter"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b => + { + b.Property("Key") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Field") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Key", "Field"); + + b.HasIndex("ExpireAt"); + + b.ToTable("HangfireHash"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("InvocationData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StateId") + .HasColumnType("bigint"); + + b.Property("StateName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ExpireAt"); + + b.HasIndex("StateId"); + + b.HasIndex("StateName"); + + b.ToTable("HangfireJob"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b => + { + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("JobId", "Name"); + + b.ToTable("HangfireJobParameter"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b => + { + b.Property("Key") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Position") + .HasColumnType("integer"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Key", "Position"); + + b.HasIndex("ExpireAt"); + + b.ToTable("HangfireList"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b => + { + b.Property("Id") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AcquiredAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("HangfireLock"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FetchedAt") + .IsConcurrencyToken() + .HasColumnType("timestamp with time zone"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("Queue") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("Queue", "FetchedAt"); + + b.ToTable("HangfireQueuedJob"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b => + { + b.Property("Id") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Heartbeat") + .HasColumnType("timestamp with time zone"); + + b.Property("Queues") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerCount") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Heartbeat"); + + b.ToTable("HangfireServer"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b => + { + b.Property("Key") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Value") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.HasKey("Key", "Value"); + + b.HasIndex("ExpireAt"); + + b.HasIndex("Key", "Score"); + + b.ToTable("HangfireSet"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Reason") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.ToTable("HangfireState"); + }); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.PrimitiveCollection("Permissions") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.ToTable("Core_ApiKeys", (string)null); + }); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("DonateUrl") + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdateUrl") + .HasColumnType("text"); + + b.Property("Version") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Core_Themes", (string)null); + }); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection("Permissions") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("TokenValidTimestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Core_Users", (string)null); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => + { + b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State") + .WithMany() + .HasForeignKey("StateId"); + + b.Navigation("State"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b => + { + b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") + .WithMany("Parameters") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b => + { + b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") + .WithMany("QueuedJobs") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b => + { + b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") + .WithMany("States") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + }); + + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => + { + b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 => + { + b1.Property("ThemeId") + .HasColumnType("integer"); + + b1.Property("Border") + .HasColumnType("real"); + + b1.Property("ColorAccent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorAccentContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBackground") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase100") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase150") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase200") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase250") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase300") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBaseContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorError") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorErrorContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorInfo") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorInfoContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorNeutral") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorNeutralContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorPrimary") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorPrimaryContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSecondary") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSecondaryContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSuccess") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSuccessContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorWarning") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorWarningContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Depth") + .HasColumnType("real"); + + b1.Property("Noise") + .HasColumnType("real"); + + b1.Property("RadiusBox") + .HasColumnType("real"); + + b1.Property("RadiusField") + .HasColumnType("real"); + + b1.Property("RadiusSelector") + .HasColumnType("real"); + + b1.Property("SizeField") + .HasColumnType("real"); + + b1.Property("SizeSelector") + .HasColumnType("real"); + + b1.HasKey("ThemeId"); + + b1.ToTable("Core_Themes"); + + b1.ToJson("Content"); + + b1.WithOwner() + .HasForeignKey("ThemeId"); + }); + + b.Navigation("Content") + .IsRequired(); + }); + + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => + { + b.Navigation("Parameters"); + + b.Navigation("QueuedJobs"); + + b.Navigation("States"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.cs b/Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.cs new file mode 100644 index 00000000..0b4b643e --- /dev/null +++ b/Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Moonlight.ApiServer.Database.Migrations +{ + /// + public partial class AddedThemes : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Core_Themes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IsEnabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "text", nullable: false), + Author = table.Column(type: "text", nullable: false), + Version = table.Column(type: "text", nullable: false), + UpdateUrl = table.Column(type: "text", nullable: true), + DonateUrl = table.Column(type: "text", nullable: true), + Content = table.Column(type: "jsonb", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Core_Themes", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Core_Themes"); + } + } +} diff --git a/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs b/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs index 7935e606..e743555e 100644 --- a/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs +++ b/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs @@ -302,6 +302,40 @@ namespace Moonlight.ApiServer.Database.Migrations b.ToTable("Core_ApiKeys", (string)null); }); + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("DonateUrl") + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdateUrl") + .HasColumnType("text"); + + b.Property("Version") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Core_Themes", (string)null); + }); + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b => { b.Property("Id") @@ -376,6 +410,143 @@ namespace Moonlight.ApiServer.Database.Migrations b.Navigation("Job"); }); + modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => + { + b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 => + { + b1.Property("ThemeId") + .HasColumnType("integer"); + + b1.Property("Border") + .HasColumnType("real"); + + b1.Property("ColorAccent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorAccentContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBackground") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase100") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase150") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase200") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase250") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBase300") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorBaseContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorError") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorErrorContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorInfo") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorInfoContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorNeutral") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorNeutralContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorPrimary") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorPrimaryContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSecondary") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSecondaryContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSuccess") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorSuccessContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorWarning") + .IsRequired() + .HasColumnType("text"); + + b1.Property("ColorWarningContent") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Depth") + .HasColumnType("real"); + + b1.Property("Noise") + .HasColumnType("real"); + + b1.Property("RadiusBox") + .HasColumnType("real"); + + b1.Property("RadiusField") + .HasColumnType("real"); + + b1.Property("RadiusSelector") + .HasColumnType("real"); + + b1.Property("SizeField") + .HasColumnType("real"); + + b1.Property("SizeSelector") + .HasColumnType("real"); + + b1.HasKey("ThemeId"); + + b1.ToTable("Core_Themes"); + + b1.ToJson("Content"); + + b1.WithOwner() + .HasForeignKey("ThemeId"); + }); + + b.Navigation("Content") + .IsRequired(); + }); + modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => { b.Navigation("Parameters"); diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs index f065d97d..06ce564e 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs @@ -1,7 +1,14 @@ -using System.Text.Json; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Moonlight.Shared.Http.Requests.Admin.Sys; +using Microsoft.EntityFrameworkCore; +using MoonCore.Exceptions; +using MoonCore.Extended.Abstractions; +using MoonCore.Models; +using Moonlight.ApiServer.Database.Entities; +using Moonlight.ApiServer.Mappers; +using Moonlight.Shared.Http.Requests.Admin.Sys.Theme; +using Moonlight.Shared.Http.Responses.Admin; namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; @@ -9,15 +16,113 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; [Route("api/admin/system/theme")] public class ThemeController : Controller { - [HttpPatch] - [Authorize(Policy = "permissions:admin.system.theme.update")] - public async Task Patch([FromBody] UpdateThemeRequest request) - { - var themePath = Path.Combine("storage", "theme.json"); + private readonly DatabaseRepository ThemeRepository; - await System.IO.File.WriteAllTextAsync( - themePath, - JsonSerializer.Serialize(request.Variables) - ); + public ThemeController(DatabaseRepository themeRepository) + { + ThemeRepository = themeRepository; + } + + [HttpGet] + [Authorize(Policy = "permissions:admin.system.theme.read")] + public async Task> Get( + [FromQuery] [Range(0, int.MaxValue)] int page, + [FromQuery] [Range(1, 100)] int pageSize + ) + { + var count = await ThemeRepository.Get().CountAsync(); + + var items = await ThemeRepository + .Get() + .Skip(page * pageSize) + .Take(pageSize) + .ToArrayAsync(); + + var mappedItems = items + .Select(ThemeMapper.ToResponse) + .ToArray(); + + return new PagedData() + { + CurrentPage = page, + Items = mappedItems, + PageSize = pageSize, + TotalItems = count, + TotalPages = count == 0 ? 0 : (count - 1) / pageSize + }; + } + + [HttpGet("{id:int}")] + [Authorize(Policy = "permissions:admin.system.theme.read")] + public async Task GetSingle([FromRoute] int id) + { + var theme = await ThemeRepository + .Get() + .FirstOrDefaultAsync(t => t.Id == id); + + if (theme == null) + throw new HttpApiException("Theme with this id not found", 404); + + return ThemeMapper.ToResponse(theme); + } + + [HttpPost] + [Authorize(Policy = "permissions:admin.system.theme.write")] + public async Task Create([FromBody] CreateThemeRequest request) + { + var theme = ThemeMapper.ToTheme(request); + + var finalTheme = await ThemeRepository.Add(theme); + + return ThemeMapper.ToResponse(finalTheme); + } + + [HttpPatch("{id:int}")] + [Authorize(Policy = "permissions:admin.system.theme.write")] + public async Task Update([FromRoute] int id, [FromBody] UpdateThemeRequest request) + { + var theme = await ThemeRepository + .Get() + .FirstOrDefaultAsync(t => t.Id == id); + + if (theme == null) + throw new HttpApiException("Theme with this id not found", 404); + + // Disable all other enabled themes if we are enabling the current theme. + // This ensures only one theme is enabled at the time + if (request.IsEnabled) + { + var otherThemes = await ThemeRepository + .Get() + .Where(x => x.IsEnabled && x.Id != id) + .ToArrayAsync(); + + foreach (var otherTheme in otherThemes) + otherTheme.IsEnabled = false; + + await ThemeRepository.RunTransaction(set => + { + set.UpdateRange(otherThemes); + }); + } + + ThemeMapper.Merge(theme, request); + await ThemeRepository.Update(theme); + + return ThemeMapper.ToResponse(theme); + } + + [HttpPost("{id:int}")] + [Authorize(Policy = "permissions:admin.system.theme.write")] + public async Task Delete([FromRoute] int id) + { + var theme = await ThemeRepository + .Get() + .FirstOrDefaultAsync(x => x.Id == id); + + if (theme == null) + throw new HttpApiException("Theme with this id not found", 404); + + await ThemeRepository.Remove(theme); } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs index 495f1370..59b42c05 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs @@ -26,7 +26,7 @@ public class UsersController : Controller [HttpGet] [Authorize(Policy = "permissions:admin.users.get")] public async Task> Get( - [FromQuery] int page, + [FromQuery] [Range(0, int.MaxValue)] int page, [FromQuery] [Range(1, 100)] int pageSize = 50 ) { diff --git a/Moonlight.ApiServer/Mappers/ThemeMapper.cs b/Moonlight.ApiServer/Mappers/ThemeMapper.cs new file mode 100644 index 00000000..3523f9db --- /dev/null +++ b/Moonlight.ApiServer/Mappers/ThemeMapper.cs @@ -0,0 +1,14 @@ +using Moonlight.ApiServer.Database.Entities; +using Moonlight.Shared.Http.Requests.Admin.Sys.Theme; +using Moonlight.Shared.Http.Responses.Admin; +using Riok.Mapperly.Abstractions; + +namespace Moonlight.ApiServer.Mappers; + +[Mapper] +public static partial class ThemeMapper +{ + public static partial ThemeResponse ToResponse(Theme theme); + public static partial Theme ToTheme(CreateThemeRequest request); + public static partial void Merge([MappingTarget] Theme theme, UpdateThemeRequest request); +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Models/ApplicationTheme.cs b/Moonlight.ApiServer/Models/ApplicationTheme.cs new file mode 100644 index 00000000..e6fc1413 --- /dev/null +++ b/Moonlight.ApiServer/Models/ApplicationTheme.cs @@ -0,0 +1,49 @@ +namespace Moonlight.ApiServer.Models; + +public class ApplicationTheme +{ + public string ColorBackground { get; set; } + + public string ColorBase100 { get; set; } + public string ColorBase150 { get; set; } + public string ColorBase200 { get; set; } + public string ColorBase250 { get; set; } + public string ColorBase300 { get; set; } + + public string ColorBaseContent { get; set; } + + public string ColorPrimary { get; set; } + public string ColorPrimaryContent { get; set; } + + public string ColorSecondary { get; set; } + public string ColorSecondaryContent { get; set; } + + public string ColorAccent { get; set; } + public string ColorAccentContent { get; set; } + + public string ColorNeutral { get; set; } + public string ColorNeutralContent { get; set; } + + public string ColorInfo { get; set; } + public string ColorInfoContent { get; set; } + + public string ColorSuccess { get; set; } + public string ColorSuccessContent { get; set; } + + public string ColorWarning { get; set; } + public string ColorWarningContent { get; set; } + + public string ColorError { get; set; } + public string ColorErrorContent { get; set; } + + public float RadiusSelector { get; set; } + public float RadiusField { get; set; } + public float RadiusBox { get; set; } + + public float SizeSelector { get; set; } + public float SizeField { get; set; } + + public float Border { get; set; } + public float Depth { get; set; } + public float Noise { get; set; } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index 9f12dfbd..505b6b6f 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -33,6 +33,7 @@ + diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj index b06901cf..e0a33fba 100644 --- a/Moonlight.Client/Moonlight.Client.csproj +++ b/Moonlight.Client/Moonlight.Client.csproj @@ -49,4 +49,9 @@ + + + + + \ No newline at end of file diff --git a/Moonlight.Client/UI/Components/ColorSelector.razor b/Moonlight.Client/UI/Components/ColorSelector.razor new file mode 100644 index 00000000..b86e5f00 --- /dev/null +++ b/Moonlight.Client/UI/Components/ColorSelector.razor @@ -0,0 +1,45 @@ + + + +@code +{ + + #region + + [Parameter] + public string? Value + { + get => _value; + set + { + if (_value?.Equals(value) ?? false) + return; + + _value = value; + ValueChanged.InvokeAsync(value); + } + } + + [Parameter] public EventCallback ValueChanged { get; set; } + + private string? _value; + + #endregion + + [Parameter] public string Icon { get; set; } = "icon-paintbrush"; + + private string Id; + + protected override void OnInitialized() + { + Id = $"color-selector-{GetHashCode()}"; + } + + private async Task Update(ChangeEventArgs args) + { + Value = args.Value?.ToString() ?? "#FFFFFF"; + await InvokeAsync(StateHasChanged); + } +} diff --git a/Moonlight.Client/UI/Components/StatCard.razor b/Moonlight.Client/UI/Components/StatCard.razor index e0d895fb..cd00edb0 100644 --- a/Moonlight.Client/UI/Components/StatCard.razor +++ b/Moonlight.Client/UI/Components/StatCard.razor @@ -13,7 +13,7 @@
@Title
-
@Text
+
@Text
@code diff --git a/Moonlight.Client/UI/Partials/Design/ThemeSettings.razor b/Moonlight.Client/UI/Partials/Design/ThemeSettings.razor deleted file mode 100644 index 9881cf0a..00000000 --- a/Moonlight.Client/UI/Partials/Design/ThemeSettings.razor +++ /dev/null @@ -1,259 +0,0 @@ -@using System.Text.Json -@using MoonCore.Helpers -@using Moonlight.Client.Services -@using Moonlight.Client.UI.Components -@using Moonlight.Shared.Http.Requests.Admin.Sys -@using Moonlight.Shared.Misc - -@inject HttpApiClient ApiClient -@inject FrontendConfiguration FrontendConfiguration -@inject ThemeService ThemeService -@inject ToastService ToastService - -@* @inject DownloadService DownloadService *@ - -
-
- - - Save - -
-
- -
- @foreach (var colorSettingGroup in GroupedColorSettings) - { -
- @foreach (var colorSetting in colorSettingGroup.Value) - { - - } -
- } -
- -@code -{ - private readonly Dictionary> GroupedColorSettings = new(); - - protected override void OnInitialized() - { - // Primary - AddSetting("primary", "primary-50", 238, 242, 255); - AddSetting("primary", "primary-100", 224, 231, 255); - AddSetting("primary", "primary-200", 199, 210, 254); - AddSetting("primary", "primary-300", 165, 180, 252); - AddSetting("primary", "primary-400", 129, 140, 248); - AddSetting("primary", "primary", 99, 102, 241); - AddSetting("primary", "primary-600", 79, 70, 229); - AddSetting("primary", "primary-700", 67, 56, 202); - AddSetting("primary", "primary-800", 55, 48, 163); - AddSetting("primary", "primary-900", 49, 46, 129); - AddSetting("primary", "primary-950", 30, 27, 75); - - // Secondary - AddSetting("secondary", "secondary-100", 249, 249, 249); - AddSetting("secondary", "secondary-200", 241, 241, 242); - AddSetting("secondary", "secondary-300", 219, 223, 233); - AddSetting("secondary", "secondary-400", 181, 181, 195); - AddSetting("secondary", "secondary-500", 153, 161, 183); - AddSetting("secondary", "secondary-600", 112, 121, 147); - AddSetting("secondary", "secondary-700", 68, 78, 107); - AddSetting("secondary", "secondary-800", 28, 36, 56); - AddSetting("secondary", "secondary-900", 17, 23, 33); - AddSetting("secondary", "secondary-950", 14, 18, 28); - - // Tertiary - AddSetting("tertiary", "tertiary-50", 245, 243, 255); - AddSetting("tertiary", "tertiary-100", 237, 233, 254); - AddSetting("tertiary", "tertiary-200", 221, 214, 254); - AddSetting("tertiary", "tertiary-300", 196, 181, 253); - AddSetting("tertiary", "tertiary-400", 167, 139, 250); - AddSetting("tertiary", "tertiary", 139, 92, 246); - AddSetting("tertiary", "tertiary-600", 124, 58, 237); - AddSetting("tertiary", "tertiary-700", 109, 40, 217); - AddSetting("tertiary", "tertiary-800", 91, 33, 182); - AddSetting("tertiary", "tertiary-900", 76, 29, 149); - AddSetting("tertiary", "tertiary-950", 46, 16, 101); - - // Warning - AddSetting("warning", "warning-50", 254, 252, 232); - AddSetting("warning", "warning-100", 254, 249, 195); - AddSetting("warning", "warning-200", 254, 240, 138); - AddSetting("warning", "warning-300", 253, 224, 71); - AddSetting("warning", "warning-400", 250, 204, 21); - AddSetting("warning", "warning-500", 234, 179, 8); - AddSetting("warning", "warning-600", 202, 138, 4); - AddSetting("warning", "warning-700", 161, 98, 7); - AddSetting("warning", "warning-800", 133, 77, 14); - AddSetting("warning", "warning-900", 113, 63, 18); - AddSetting("warning", "warning-950", 66, 32, 6); - - // Danger - AddSetting("danger", "danger-50", 254, 242, 242); - AddSetting("danger", "danger-100", 254, 226, 226); - AddSetting("danger", "danger-200", 254, 202, 202); - AddSetting("danger", "danger-300", 252, 165, 165); - AddSetting("danger", "danger-400", 248, 113, 113); - AddSetting("danger", "danger", 239, 68, 68); - AddSetting("danger", "error", 220, 38, 38); - AddSetting("danger", "danger-700", 185, 28, 28); - AddSetting("danger", "danger-800", 153, 27, 27); - AddSetting("danger", "danger-900", 127, 29, 29); - AddSetting("danger", "danger-950", 69, 10, 10); - - // Success - AddSetting("success", "success-50", 240, 253, 244); - AddSetting("success", "success-100", 220, 252, 231); - AddSetting("success", "success-200", 187, 247, 208); - AddSetting("success", "success-300", 134, 239, 172); - AddSetting("success", "success-400", 74, 222, 128); - AddSetting("success", "success", 34, 197, 94); - AddSetting("success", "success-600", 22, 163, 74); - AddSetting("success", "success-700", 21, 128, 61); - AddSetting("success", "success-800", 22, 101, 52); - AddSetting("success", "success-900", 20, 83, 45); - AddSetting("success", "success-950", 5, 46, 22); - - // Info - AddSetting("info", "info-50", 239, 246, 255); - AddSetting("info", "info-100", 219, 234, 254); - AddSetting("info", "info-200", 191, 219, 254); - AddSetting("info", "info-300", 147, 197, 253); - AddSetting("info", "info-400", 96, 165, 250); - AddSetting("info", "info-500", 59, 130, 246); - AddSetting("info", "info-600", 37, 99, 235); - AddSetting("info", "info-700", 29, 78, 216); - AddSetting("info", "info-800", 30, 64, 175); - AddSetting("info", "info-900", 30, 58, 138); - AddSetting("info", "info-950", 23, 37, 84); - - // Gray - AddSetting("gray", "gray-100", 249, 249, 249); - AddSetting("gray", "gray-200", 241, 241, 242); - AddSetting("gray", "gray-300", 219, 223, 233); - AddSetting("gray", "gray-400", 181, 181, 195); - AddSetting("gray", "gray-500", 153, 161, 183); - AddSetting("gray", "gray-600", 112, 121, 147); - AddSetting("gray", "gray-700", 68, 78, 107); - AddSetting("gray", "gray-750", 41, 50, 73); - AddSetting("gray", "gray-800", 28, 36, 56); - AddSetting("gray", "gray-900", 17, 23, 33); - AddSetting("gray", "gray-950", 14, 18, 28); - - // Full - AddSetting("full", "light", 255, 255, 255); - AddSetting("full", "dark", 0, 0, 0); - } - - private void AddSetting(string group, string identifier, int r, int g, int b) - { - if (!GroupedColorSettings.ContainsKey(group)) - GroupedColorSettings[group] = new(); - - var value = ThemeService.Variables.GetValueOrDefault(identifier); - - GroupedColorSettings[group].Add(new ColorSetting() - { - Identifier = identifier, - DefaultValue = $"{r} {g} {b}", - Value = value - }); - } - - private async Task OnChanged(ColorSetting colorSetting, string color) - { - if (color == colorSetting.DefaultValue) - colorSetting.Value = null; - else - colorSetting.Value = color; - - if (colorSetting.Value == null && ThemeService.Variables.ContainsKey(colorSetting.Identifier)) - ThemeService.Variables.Remove(colorSetting.Identifier); - else if (colorSetting.Value != null) - ThemeService.Variables[colorSetting.Identifier] = colorSetting.Value; - - await ThemeService.Refresh(); - } - - private async Task Save() - { - if (FrontendConfiguration.HostEnvironment != "ApiServer") - { - await ToastService.Error( - "Theme Settings", - "Unable to save the theme settings. If you are using a static host, you need to configure the colors in the frontend.json file" - ); - - return; - } - - // Send new variables - await ApiClient.Patch("api/admin/system/theme", new UpdateThemeRequest() - { - Variables = ThemeService.Variables - }); - - await ToastService.Success("Successfully saved theme settings"); - } - - private async Task Export() - { - // Serialize the variables - var json = JsonSerializer.Serialize(ThemeService.Variables); - - // Download the theme configuration - //await DownloadService.DownloadString("theme.json", json); - - await ToastService.Success("Successfully exported theme configuration"); - } - - private async Task Import(InputFileChangeEventArgs eventArgs) - { - if (!eventArgs.File.Name.EndsWith(".json")) - { - await ToastService.Error("Only .json files are allowed"); - return; - } - - // Read file content - var stream = eventArgs.File.OpenReadStream(); - var sr = new StreamReader(stream); - var json = await sr.ReadToEndAsync(); - - // Deserialize - var variables = JsonSerializer.Deserialize>(json) ?? new(); - - // Update variables - ThemeService.Variables.Clear(); - - foreach (var variable in variables) - ThemeService.Variables[variable.Key] = variable.Value; - - // Apply changes - await ThemeService.Refresh(); - - // - await ToastService.Success("Successfully imported theme configuration"); - } - - class ColorSetting - { - public string Identifier { get; set; } - public string DefaultValue { get; set; } - public string? Value { get; set; } - } -} diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Theme.razor b/Moonlight.Client/UI/Views/Admin/Sys/Theme.razor index d1d1bad4..73947c38 100644 --- a/Moonlight.Client/UI/Views/Admin/Sys/Theme.razor +++ b/Moonlight.Client/UI/Views/Admin/Sys/Theme.razor @@ -1,23 +1,197 @@ @page "/admin/system/theme" @using Microsoft.AspNetCore.Authorization -@using Moonlight.Client.UI.Partials.Design +@using Moonlight.Client.UI.Components +@using Moonlight.Shared.Misc @attribute [Authorize(Policy = "permissions:admin.system.theme")] -
- -
+ -
-
-
-
https://your-moonlight.instance
+
+
+
+ + Background
-
- + +
+ + Base Content +
+ +
+ + Base 100 +
+ +
+ + Base 150 +
+ +
+ + Base 200 +
+ +
+ + Base 250 +
+ +
+ + Base 300 +
+
+ +
+
+
+ + Primary +
+ +
+ + Primary Content +
+
+ +
+
+ + Secondary +
+ +
+ + Secondary Content +
+
+ +
+
+ + Accent +
+ +
+ + Accent Content +
+
+ +
+
+ + Info +
+ +
+ + Info Content +
+
+ +
+
+ + Success +
+ +
+ + Success Content +
+
+ +
+
+ + Warning +
+ +
+ + Warning Content +
+
+ +
+
+ + Error +
+ +
+ + Error Content +
+ + + +@code +{ + private ApplicationTheme ThemeData; + + protected override void OnInitialized() + { + ThemeData = CreateDefault(); + } + + private ApplicationTheme CreateDefault() + { + return new ApplicationTheme() + { + ColorBackground = "#0c0f18", + + ColorBase100 = "#1e2b47", + ColorBase150 = "#1a2640", + ColorBase200 = "#101a2e", + ColorBase250 = "#0f1729", + ColorBase300 = "#0c1221", + + ColorBaseContent = "#dde5f5", + + ColorPrimary = "#4f39f6", + ColorPrimaryContent = "#dde5f5", + + ColorSecondary = "#354052", + ColorSecondaryContent = "#dde5f5", + + ColorAccent = "#ad46ff", + ColorAccentContent = "#dde5f5", + + ColorNeutral = "#dde5f5", + ColorNeutralContent = "#09090b", + + ColorInfo = "#155dfc", + ColorInfoContent = "#dde5f5", + + ColorSuccess = "#00a63e", + ColorSuccessContent = "#dde5f5", + + ColorWarning = "#ffba00", + ColorWarningContent = "#dde5f5", + + ColorError = "#ec003f", + ColorErrorContent = "#dde5f5", + + RadiusSelector = 0.25f, + RadiusField = 0.5f, + RadiusBox = 0.5f, + + SizeSelector = 0.25f, + SizeField = 0.25f, + + Border = 1f, + Depth = 0f, + Noise = 0f + }; + } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/CreateThemeRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/CreateThemeRequest.cs new file mode 100644 index 00000000..ce884e5f --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/CreateThemeRequest.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using Moonlight.Shared.Misc; + +namespace Moonlight.Shared.Http.Requests.Admin.Sys.Theme; + +public class CreateThemeRequest +{ + [Required(ErrorMessage = "You need to provide a name")] + public string Name { get; set; } + + [Required(ErrorMessage = "You need to provide an author")] + public string Author { get; set; } + + [Required(ErrorMessage = "You need to provide a version")] + public string Version { get; set; } + + public string? UpdateUrl { get; set; } + public string? DonateUrl { get; set; } + + public ApplicationTheme Content { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/UpdateThemeRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/UpdateThemeRequest.cs new file mode 100644 index 00000000..9d800336 --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/UpdateThemeRequest.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using Moonlight.Shared.Misc; + +namespace Moonlight.Shared.Http.Requests.Admin.Sys.Theme; + +public class UpdateThemeRequest +{ + public bool IsEnabled { get; set; } + + [Required(ErrorMessage = "You need to provide a name")] + public string Name { get; set; } + + [Required(ErrorMessage = "You need to provide an author")] + public string Author { get; set; } + + [Required(ErrorMessage = "You need to provide a version")] + public string Version { get; set; } + + public string? UpdateUrl { get; set; } + public string? DonateUrl { get; set; } + + public ApplicationTheme Content { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/UpdateThemeRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/UpdateThemeRequest.cs deleted file mode 100644 index ac9b76eb..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Sys/UpdateThemeRequest.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.Sys; - -public class UpdateThemeRequest -{ - [Required(ErrorMessage = "You need to provide Variables")] - public Dictionary Variables { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs b/Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs new file mode 100644 index 00000000..47253750 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs @@ -0,0 +1,19 @@ +using Moonlight.Shared.Misc; + +namespace Moonlight.Shared.Http.Responses.Admin; + +public class ThemeResponse +{ + public int Id { get; set; } + + public bool IsEnabled { get; set; } + + public string Name { get; set; } + public string Author { get; set; } + public string Version { get; set; } + + public string? UpdateUrl { get; set; } + public string? DonateUrl { get; set; } + + public ApplicationTheme Content { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Misc/ApplicationTheme.cs b/Moonlight.Shared/Misc/ApplicationTheme.cs new file mode 100644 index 00000000..ee8ac295 --- /dev/null +++ b/Moonlight.Shared/Misc/ApplicationTheme.cs @@ -0,0 +1,49 @@ +namespace Moonlight.Shared.Misc; + +public class ApplicationTheme +{ + public string ColorBackground { get; set; } + + public string ColorBase100 { get; set; } + public string ColorBase150 { get; set; } + public string ColorBase200 { get; set; } + public string ColorBase250 { get; set; } + public string ColorBase300 { get; set; } + + public string ColorBaseContent { get; set; } + + public string ColorPrimary { get; set; } + public string ColorPrimaryContent { get; set; } + + public string ColorSecondary { get; set; } + public string ColorSecondaryContent { get; set; } + + public string ColorAccent { get; set; } + public string ColorAccentContent { get; set; } + + public string ColorNeutral { get; set; } + public string ColorNeutralContent { get; set; } + + public string ColorInfo { get; set; } + public string ColorInfoContent { get; set; } + + public string ColorSuccess { get; set; } + public string ColorSuccessContent { get; set; } + + public string ColorWarning { get; set; } + public string ColorWarningContent { get; set; } + + public string ColorError { get; set; } + public string ColorErrorContent { get; set; } + + public float RadiusSelector { get; set; } + public float RadiusField { get; set; } + public float RadiusBox { get; set; } + + public float SizeSelector { get; set; } + public float SizeField { get; set; } + + public float Border { get; set; } + public float Depth { get; set; } + public float Noise { get; set; } +} \ No newline at end of file