Expanding theme tab to customization tab. Started improving theme selection.
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using MoonCore.Extended.SingleDb;
|
using MoonCore.Extended.SingleDb;
|
||||||
using Moonlight.ApiServer.Configuration;
|
using Moonlight.ApiServer.Configuration;
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
namespace Moonlight.ApiServer.Database;
|
namespace Moonlight.ApiServer.Database;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ public class CoreDataContext : DatabaseContext
|
|||||||
|
|
||||||
public DbSet<User> Users { get; set; }
|
public DbSet<User> Users { get; set; }
|
||||||
public DbSet<ApiKey> ApiKeys { get; set; }
|
public DbSet<ApiKey> ApiKeys { get; set; }
|
||||||
|
public DbSet<Theme> Themes { get; set; }
|
||||||
|
|
||||||
public CoreDataContext(AppConfiguration configuration)
|
public CoreDataContext(AppConfiguration configuration)
|
||||||
{
|
{
|
||||||
@@ -29,5 +31,12 @@ public class CoreDataContext : DatabaseContext
|
|||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
modelBuilder.OnHangfireModelCreating();
|
modelBuilder.OnHangfireModelCreating();
|
||||||
|
|
||||||
|
modelBuilder.Ignore<ApplicationTheme>();
|
||||||
|
modelBuilder.Entity<Theme>()
|
||||||
|
.OwnsOne(x => x.Content, builder =>
|
||||||
|
{
|
||||||
|
builder.ToJson();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
19
Moonlight.ApiServer/Database/Entities/Theme.cs
Normal file
19
Moonlight.ApiServer/Database/Entities/Theme.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
564
Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs
generated
Normal file
564
Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs
generated
Normal file
@@ -0,0 +1,564 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<long>("Value")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Value");
|
||||||
|
|
||||||
|
b.ToTable("HangfireCounter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Field")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Field");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireHash");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("InvocationData")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("StateId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("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<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("JobId", "Name");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJobParameter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Position");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireList");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AcquiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("HangfireLock");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FetchedAt")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("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<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Heartbeat")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Queues")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("WorkerCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Heartbeat");
|
||||||
|
|
||||||
|
b.ToTable("HangfireServer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<double>("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<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Data")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.ToTable("HangfireState");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Author")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("DonateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UpdateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Version")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Themes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("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<int>("ThemeId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<float>("Border")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccentContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBackground")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase100")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase150")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase200")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase250")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase300")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBaseContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorError")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorErrorContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfo")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfoContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutral")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutralContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccess")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccessContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarning")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarningContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<float>("Depth")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("Noise")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusBox")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedThemes : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Core_Themes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Author = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Version = table.Column<string>(type: "text", nullable: false),
|
||||||
|
UpdateUrl = table.Column<string>(type: "text", nullable: true),
|
||||||
|
DonateUrl = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Content = table.Column<string>(type: "jsonb", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Core_Themes", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Core_Themes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -302,6 +302,40 @@ namespace Moonlight.ApiServer.Database.Migrations
|
|||||||
b.ToTable("Core_ApiKeys", (string)null);
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Author")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("DonateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UpdateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Version")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Themes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -376,6 +410,143 @@ namespace Moonlight.ApiServer.Database.Migrations
|
|||||||
b.Navigation("Job");
|
b.Navigation("Job");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("ThemeId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<float>("Border")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccentContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBackground")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase100")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase150")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase200")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase250")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase300")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBaseContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorError")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorErrorContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfo")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfoContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutral")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutralContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccess")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccessContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarning")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarningContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<float>("Depth")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("Noise")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusBox")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("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 =>
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Parameters");
|
b.Navigation("Parameters");
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
using System.Text.Json;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
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;
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||||
|
|
||||||
@@ -9,15 +16,113 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
|||||||
[Route("api/admin/system/theme")]
|
[Route("api/admin/system/theme")]
|
||||||
public class ThemeController : Controller
|
public class ThemeController : Controller
|
||||||
{
|
{
|
||||||
[HttpPatch]
|
private readonly DatabaseRepository<Theme> ThemeRepository;
|
||||||
[Authorize(Policy = "permissions:admin.system.theme.update")]
|
|
||||||
public async Task Patch([FromBody] UpdateThemeRequest request)
|
|
||||||
{
|
|
||||||
var themePath = Path.Combine("storage", "theme.json");
|
|
||||||
|
|
||||||
await System.IO.File.WriteAllTextAsync(
|
public ThemeController(DatabaseRepository<Theme> themeRepository)
|
||||||
themePath,
|
{
|
||||||
JsonSerializer.Serialize(request.Variables)
|
ThemeRepository = themeRepository;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.theme.read")]
|
||||||
|
public async Task<PagedData<ThemeResponse>> 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<ThemeResponse>()
|
||||||
|
{
|
||||||
|
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<ThemeResponse> 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<ThemeResponse> 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<ThemeResponse> 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ public class UsersController : Controller
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize(Policy = "permissions:admin.users.get")]
|
[Authorize(Policy = "permissions:admin.users.get")]
|
||||||
public async Task<IPagedData<UserResponse>> Get(
|
public async Task<IPagedData<UserResponse>> Get(
|
||||||
[FromQuery] int page,
|
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||||
[FromQuery] [Range(1, 100)] int pageSize = 50
|
[FromQuery] [Range(1, 100)] int pageSize = 50
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|||||||
14
Moonlight.ApiServer/Mappers/ThemeMapper.cs
Normal file
14
Moonlight.ApiServer/Mappers/ThemeMapper.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
49
Moonlight.ApiServer/Models/ApplicationTheme.cs
Normal file
49
Moonlight.ApiServer/Models/ApplicationTheme.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1" />
|
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||||
|
<PackageReference Include="Riok.Mapperly" Version="4.3.0-next.2" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
|
||||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||||
|
|||||||
@@ -49,4 +49,9 @@
|
|||||||
<None Include="Styles/mappings/*.map" Pack="true" PackagePath="Styles/" />
|
<None Include="Styles/mappings/*.map" Pack="true" PackagePath="Styles/" />
|
||||||
<None Include="Moonlight.Client.targets" Pack="true" PackagePath="build/Moonlight.Client.targets" />
|
<None Include="Moonlight.Client.targets" Pack="true" PackagePath="build/Moonlight.Client.targets" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<UpToDateCheckInput Remove="UI\Views\Internal\Mockup.razor" />
|
||||||
|
<UpToDateCheckInput Remove="UI\Partials\Design\ThemeSettings.razor" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
45
Moonlight.Client/UI/Components/ColorSelector.razor
Normal file
45
Moonlight.Client/UI/Components/ColorSelector.razor
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<label for="@Id" class="btn btn-square border-0 ring-0 outline-0" style="background-color: @Value">
|
||||||
|
<i class="text-lg text-base-content @Icon"></i>
|
||||||
|
</label>
|
||||||
|
<input value="@Value" @oninput="Update" id="@Id" type="color" class="h-0 w-0 opacity-0"/>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
|
||||||
|
#region
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string? Value
|
||||||
|
{
|
||||||
|
get => _value;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_value?.Equals(value) ?? false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_value = value;
|
||||||
|
ValueChanged.InvokeAsync(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Parameter] public EventCallback<string?> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<span class="@Icon text-4xl text-primary"></span>
|
<span class="@Icon text-4xl text-primary"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">@Title</div>
|
<div class="stat-title">@Title</div>
|
||||||
<div class="stat-value text-xl!">@Text</div>
|
<div class="stat-value truncate text-xl!">@Text</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
|
|||||||
@@ -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 *@
|
|
||||||
|
|
||||||
<div class="card card-body p-2">
|
|
||||||
<div class="flex flex-row items-center justify-end gap-x-2">
|
|
||||||
<WButton OnClick="_ => Save()" CssClasses="btn btn-success">
|
|
||||||
<i class="icon-save me-1"></i>
|
|
||||||
<span>Save</span>
|
|
||||||
</WButton>
|
|
||||||
<InputFile OnChange="Import" id="import-file" hidden="hidden" />
|
|
||||||
<label for="import-file" class="btn btn-info cursor-pointer">
|
|
||||||
<i class="icon-file-up me-1"></i>
|
|
||||||
<span>Import</span>
|
|
||||||
</label>
|
|
||||||
<WButton OnClick="_ => Export()" CssClasses="btn btn-warning">
|
|
||||||
<i class="icon-download me-1"></i>
|
|
||||||
<span>Export</span>
|
|
||||||
</WButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
|
|
||||||
@foreach (var colorSettingGroup in GroupedColorSettings)
|
|
||||||
{
|
|
||||||
<div class="flex flex-col gap-y-2">
|
|
||||||
@foreach (var colorSetting in colorSettingGroup.Value)
|
|
||||||
{
|
|
||||||
<ThemeColorSelector Identifier="@colorSetting.Identifier"
|
|
||||||
Value="@colorSetting.Value"
|
|
||||||
DefaultValue="@colorSetting.DefaultValue"
|
|
||||||
OnChanged="color => OnChanged(colorSetting, color)"/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
private readonly Dictionary<string, List<ColorSetting>> 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<Dictionary<string, string>>(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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,197 @@
|
|||||||
@page "/admin/system/theme"
|
@page "/admin/system/theme"
|
||||||
|
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@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")]
|
@attribute [Authorize(Policy = "permissions:admin.system.theme")]
|
||||||
|
|
||||||
<div class="mb-5">
|
<NavTabs Index="1" Names="UiConstants.AdminNavNames" Links="UiConstants.AdminNavLinks"/>
|
||||||
<NavTabs Index="1" Names="UiConstants.AdminNavNames" Links="UiConstants.AdminNavLinks" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5">
|
<div class="mt-5 grid grid-cols-3 gap-3">
|
||||||
<div class="mockup-browser bg-base-300/40">
|
<div class="col-span-1 flex flex-col gap-3">
|
||||||
<div class="mockup-browser-toolbar">
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
<div class="input bg-base-200">https://your-moonlight.instance</div>
|
<ColorSelector @bind-Value="ThemeData.ColorBackground"/>
|
||||||
|
<span class="ms-1">Background</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex h-80 justify-center">
|
|
||||||
<iframe src="http://localhost:5165/admin/system" class="w-full object-cover" >
|
|
||||||
|
|
||||||
</iframe>
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorBaseContent"/>
|
||||||
|
<span class="ms-1">Base Content</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorBase100"/>
|
||||||
|
<span class="ms-1">Base 100</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorBase150"/>
|
||||||
|
<span class="ms-1">Base 150</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorBase200"/>
|
||||||
|
<span class="ms-1">Base 200</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorBase250"/>
|
||||||
|
<span class="ms-1">Base 250</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorBase300"/>
|
||||||
|
<span class="ms-1">Base 300</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-2 grid grid-cols-2 gap-3">
|
||||||
|
<div class="col-span-1 flex flex-col gap-y-3">
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorPrimary"/>
|
||||||
|
<span class="ms-1">Primary</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector Icon="icon-type" @bind-Value="ThemeData.ColorPrimaryContent"/>
|
||||||
|
<span class="ms-1">Primary Content</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 flex flex-col gap-y-3">
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorSecondary"/>
|
||||||
|
<span class="ms-1">Secondary</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector Icon="icon-type" @bind-Value="ThemeData.ColorSecondaryContent"/>
|
||||||
|
<span class="ms-1">Secondary Content</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 flex flex-col gap-y-3">
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorAccent"/>
|
||||||
|
<span class="ms-1">Accent</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector Icon="icon-type" @bind-Value="ThemeData.ColorAccentContent"/>
|
||||||
|
<span class="ms-1">Accent Content</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 flex flex-col gap-y-3">
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorInfo"/>
|
||||||
|
<span class="ms-1">Info</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector Icon="icon-type" @bind-Value="ThemeData.ColorInfoContent"/>
|
||||||
|
<span class="ms-1">Info Content</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 flex flex-col gap-y-3">
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorSuccess"/>
|
||||||
|
<span class="ms-1">Success</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector Icon="icon-type" @bind-Value="ThemeData.ColorSuccessContent"/>
|
||||||
|
<span class="ms-1">Success Content</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 flex flex-col gap-y-3">
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorWarning"/>
|
||||||
|
<span class="ms-1">Warning</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector Icon="icon-type" @bind-Value="ThemeData.ColorWarningContent"/>
|
||||||
|
<span class="ms-1">Warning Content</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 flex flex-col gap-y-3">
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector @bind-Value="ThemeData.ColorError"/>
|
||||||
|
<span class="ms-1">Error</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card card-body flex-row grow-0 items-center p-1.5 text-base-content">
|
||||||
|
<ColorSelector Icon="icon-type" @bind-Value="ThemeData.ColorErrorContent"/>
|
||||||
|
<span class="ms-1">Error Content</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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<string, string> Variables { get; set; }
|
|
||||||
}
|
|
||||||
19
Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs
Normal file
19
Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
49
Moonlight.Shared/Misc/ApplicationTheme.cs
Normal file
49
Moonlight.Shared/Misc/ApplicationTheme.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user