From a67829035e39bbdd2e8f6ee235cfee122699307c Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Fri, 22 Dec 2023 14:27:37 +0100 Subject: [PATCH 1/4] Implemented basic theme feature. Missing are import, export and asset proxy --- Moonlight/App/Configuration/ConfigV1.cs | 6 + Moonlight/App/Database/DataContext.cs | 3 + Moonlight/App/Database/Entities/Theme.cs | 13 + .../20231222100225_AddThemeModel.Designer.cs | 697 ++++++++++++++++++ .../20231222100225_AddThemeModel.cs | 39 + .../Migrations/DataContextModelSnapshot.cs | 32 + .../Models/Abstractions/ApplicationTheme.cs | 12 + .../Forms/Admin/Sys/Themes/AddThemeForm.cs | 23 + .../Forms/Admin/Sys/Themes/EditThemeForm.cs | 26 + .../App/Services/Sys/MoonlightService.cs | 8 +- .../App/Services/Sys/MoonlightThemeService.cs | 57 ++ Moonlight/Pages/_Host.cshtml | 30 +- Moonlight/Program.cs | 9 +- .../Navigations/AdminSysNavigation.razor | 5 + Moonlight/Shared/Views/Admin/Sys/Themes.razor | 72 ++ Moonlight/wwwroot/css/demotheme.css | 3 + 16 files changed, 1025 insertions(+), 10 deletions(-) create mode 100644 Moonlight/App/Database/Entities/Theme.cs create mode 100644 Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.Designer.cs create mode 100644 Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.cs create mode 100644 Moonlight/App/Models/Abstractions/ApplicationTheme.cs create mode 100644 Moonlight/App/Models/Forms/Admin/Sys/Themes/AddThemeForm.cs create mode 100644 Moonlight/App/Models/Forms/Admin/Sys/Themes/EditThemeForm.cs create mode 100644 Moonlight/App/Services/Sys/MoonlightThemeService.cs create mode 100644 Moonlight/Shared/Views/Admin/Sys/Themes.razor create mode 100644 Moonlight/wwwroot/css/demotheme.css diff --git a/Moonlight/App/Configuration/ConfigV1.cs b/Moonlight/App/Configuration/ConfigV1.cs index 1cbd38e..7818130 100644 --- a/Moonlight/App/Configuration/ConfigV1.cs +++ b/Moonlight/App/Configuration/ConfigV1.cs @@ -16,6 +16,12 @@ public class ConfigV1 [JsonProperty("Store")] public StoreData Store { get; set; } = new(); + [JsonProperty("Theme")] public ThemeData Theme { get; set; } = new(); + public class ThemeData + { + [JsonProperty("EnableDefault")] public bool EnableDefault { get; set; } = true; + } + public class StoreData { [JsonProperty("Currency")] diff --git a/Moonlight/App/Database/DataContext.cs b/Moonlight/App/Database/DataContext.cs index a0ea380..de9bf50 100644 --- a/Moonlight/App/Database/DataContext.cs +++ b/Moonlight/App/Database/DataContext.cs @@ -34,6 +34,9 @@ public class DataContext : DbContext // Tickets public DbSet Tickets { get; set; } public DbSet TicketMessages { get; set; } + + // Themes + public DbSet Themes { get; set; } public DataContext(ConfigService configService) { diff --git a/Moonlight/App/Database/Entities/Theme.cs b/Moonlight/App/Database/Entities/Theme.cs new file mode 100644 index 0000000..81a17b3 --- /dev/null +++ b/Moonlight/App/Database/Entities/Theme.cs @@ -0,0 +1,13 @@ +namespace Moonlight.App.Database.Entities; + +public class Theme +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public string Author { get; set; } = ""; + public string? DonateUrl { get; set; } = ""; + public string CssUrl { get; set; } = ""; + public string? JsUrl { get; set; } = ""; + + public bool Enabled { get; set; } = false; +} \ No newline at end of file diff --git a/Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.Designer.cs b/Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.Designer.cs new file mode 100644 index 0000000..33208aa --- /dev/null +++ b/Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.Designer.cs @@ -0,0 +1,697 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20231222100225_AddThemeModel")] + partial class AddThemeModel + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.2"); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PostId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("PostId"); + + b.ToTable("PostComments"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PostId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("PostLikes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.WordFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Filter") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WordFilters"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Coupon", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Percent") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Coupons"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CouponId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CouponId"); + + b.HasIndex("UserId"); + + b.ToTable("CouponUses"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("GiftCodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCodeUse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GiftCodeId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GiftCodeId"); + + b.HasIndex("UserId"); + + b.ToTable("GiftCodeUses"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("MaxPerUser") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("REAL"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Stock") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConfigJsonOverride") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Nickname") + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .HasColumnType("INTEGER"); + + b.Property("ProductId") + .HasColumnType("INTEGER"); + + b.Property("RenewAt") + .HasColumnType("TEXT"); + + b.Property("Suspended") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ProductId"); + + b.ToTable("Services"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.ServiceShare", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ServiceId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId"); + + b.HasIndex("UserId"); + + b.ToTable("ServiceShares"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Price") + .HasColumnType("REAL"); + + b.Property("Text") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CssUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DonateUrl") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("JsUrl") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Themes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Open") + .HasColumnType("INTEGER"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("ServiceId") + .HasColumnType("INTEGER"); + + b.Property("Tries") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("ServiceId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attachment") + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsSupport") + .HasColumnType("INTEGER"); + + b.Property("SenderId") + .HasColumnType("INTEGER"); + + b.Property("TicketId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SenderId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Avatar") + .HasColumnType("TEXT"); + + b.Property("Balance") + .HasColumnType("REAL"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Flags") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("INTEGER"); + + b.Property("TokenValidTimestamp") + .HasColumnType("TEXT"); + + b.Property("TotpKey") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.Post", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.Community.Post", null) + .WithMany("Comments") + .HasForeignKey("PostId"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostLike", b => + { + b.HasOne("Moonlight.App.Database.Entities.Community.Post", null) + .WithMany("Likes") + .HasForeignKey("PostId"); + + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.Coupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", null) + .WithMany("CouponUses") + .HasForeignKey("UserId"); + + b.Navigation("Coupon"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCodeUse", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.GiftCode", "GiftCode") + .WithMany() + .HasForeignKey("GiftCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", null) + .WithMany("GiftCodeUses") + .HasForeignKey("UserId"); + + b.Navigation("GiftCode"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Product", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.Store.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.ServiceShare", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.Service", null) + .WithMany("Shares") + .HasForeignKey("ServiceId"); + + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Transaction", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", null) + .WithMany("Transactions") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.Ticket", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.Store.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId"); + + b.Navigation("Creator"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.TicketMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.HasOne("Moonlight.App.Database.Entities.Tickets.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.Post", b => + { + b.Navigation("Comments"); + + b.Navigation("Likes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b => + { + b.Navigation("Shares"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.Ticket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Navigation("CouponUses"); + + b.Navigation("GiftCodeUses"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.cs b/Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.cs new file mode 100644 index 0000000..9aadbbb --- /dev/null +++ b/Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddThemeModel : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Themes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + Author = table.Column(type: "TEXT", nullable: false), + DonateUrl = table.Column(type: "TEXT", nullable: true), + CssUrl = table.Column(type: "TEXT", nullable: false), + JsUrl = table.Column(type: "TEXT", nullable: true), + Enabled = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Themes", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Themes"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs index 6e5d842..3ca1af2 100644 --- a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs +++ b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs @@ -357,6 +357,38 @@ namespace Moonlight.App.Database.Migrations b.ToTable("Transaction"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CssUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DonateUrl") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("JsUrl") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Themes"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.Ticket", b => { b.Property("Id") diff --git a/Moonlight/App/Models/Abstractions/ApplicationTheme.cs b/Moonlight/App/Models/Abstractions/ApplicationTheme.cs new file mode 100644 index 0000000..7f6725d --- /dev/null +++ b/Moonlight/App/Models/Abstractions/ApplicationTheme.cs @@ -0,0 +1,12 @@ +namespace Moonlight.App.Models.Abstractions; + +public class ApplicationTheme +{ + public string Name { get; set; } = ""; + public string Author { get; set; } = ""; + public string? DonateUrl { get; set; } = ""; + public string CssUrl { get; set; } = ""; + public string? JsUrl { get; set; } = ""; + + public bool Enabled { get; set; } = false; +} \ No newline at end of file diff --git a/Moonlight/App/Models/Forms/Admin/Sys/Themes/AddThemeForm.cs b/Moonlight/App/Models/Forms/Admin/Sys/Themes/AddThemeForm.cs new file mode 100644 index 0000000..7f048aa --- /dev/null +++ b/Moonlight/App/Models/Forms/Admin/Sys/Themes/AddThemeForm.cs @@ -0,0 +1,23 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.App.Models.Forms.Admin.Sys.Themes; + +public class AddThemeForm +{ + [Required(ErrorMessage = "You need to specify a name for your theme")] + public string Name { get; set; } = ""; + + [Required(ErrorMessage = "You need to specify an author for your theme")] + public string Author { get; set; } = ""; + + [Description("Enter a url to date for your theme here in order to show up when other people use this theme")] + public string? DonateUrl { get; set; } = ""; + + [Required(ErrorMessage = "You need to specify a style sheet url")] + [Description("A url to your stylesheet")] + public string CssUrl { get; set; } = ""; + + [Description("(Optional) A url to your javascript file")] + public string? JsUrl { get; set; } = null; +} \ No newline at end of file diff --git a/Moonlight/App/Models/Forms/Admin/Sys/Themes/EditThemeForm.cs b/Moonlight/App/Models/Forms/Admin/Sys/Themes/EditThemeForm.cs new file mode 100644 index 0000000..ac846b4 --- /dev/null +++ b/Moonlight/App/Models/Forms/Admin/Sys/Themes/EditThemeForm.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.App.Models.Forms.Admin.Sys.Themes; + +public class EditThemeForm +{ + [Required(ErrorMessage = "You need to specify a name for your theme")] + public string Name { get; set; } = ""; + + [Required(ErrorMessage = "You need to specify an author for your theme")] + public string Author { get; set; } = ""; + + [Description("Enter a url to date for your theme here in order to show up when other people use this theme")] + public string? DonateUrl { get; set; } = ""; + + [Required(ErrorMessage = "You need to specify a style sheet url")] + [Description("A url to your stylesheet")] + public string CssUrl { get; set; } = ""; + + [Description("(Optional) A url to your javascript file")] + public string? JsUrl { get; set; } = null; + + [Description("Enable the theme for this instance")] + public bool Enabled { get; set; } = false; +} \ No newline at end of file diff --git a/Moonlight/App/Services/Sys/MoonlightService.cs b/Moonlight/App/Services/Sys/MoonlightService.cs index 4e2db0f..a29f4ea 100644 --- a/Moonlight/App/Services/Sys/MoonlightService.cs +++ b/Moonlight/App/Services/Sys/MoonlightService.cs @@ -7,11 +7,15 @@ namespace Moonlight.App.Services.Sys; public class MoonlightService // This service can be used to perform strictly panel specific actions { private readonly ConfigService ConfigService; - public WebApplication Application { get; set; } // Do NOT modify using a plugin + private readonly IServiceProvider ServiceProvider; - public MoonlightService(ConfigService configService) + public WebApplication Application { get; set; } // Do NOT modify using a plugin + public MoonlightThemeService Theme { get; set; } + + public MoonlightService(ConfigService configService, IServiceProvider serviceProvider) { ConfigService = configService; + ServiceProvider = serviceProvider; } public async Task Restart() diff --git a/Moonlight/App/Services/Sys/MoonlightThemeService.cs b/Moonlight/App/Services/Sys/MoonlightThemeService.cs new file mode 100644 index 0000000..9d200e6 --- /dev/null +++ b/Moonlight/App/Services/Sys/MoonlightThemeService.cs @@ -0,0 +1,57 @@ +using Moonlight.App.Database.Entities; +using Moonlight.App.Models.Abstractions; +using Moonlight.App.Repositories; + +namespace Moonlight.App.Services.Sys; + +public class MoonlightThemeService +{ + private readonly IServiceProvider ServiceProvider; + private readonly ConfigService ConfigService; + + public MoonlightThemeService(IServiceProvider serviceProvider, ConfigService configService) + { + ServiceProvider = serviceProvider; + ConfigService = configService; + } + + public Task GetInstalled() + { + using var scope = ServiceProvider.CreateScope(); + var themeRepo = scope.ServiceProvider.GetRequiredService>(); + + var themes = new List(); + + themes.AddRange(themeRepo + .Get() + .ToArray() + .Select(x => new ApplicationTheme() + { + Name = x.Name, + Author = x.Author, + CssUrl = x.CssUrl, + JsUrl = x.JsUrl, + Enabled = x.Enabled, + DonateUrl = x.DonateUrl + })); + + if (ConfigService.Get().Theme.EnableDefault) + { + themes.Insert(0, new() + { + Name = "Moonlight Default", + Author = "MasuOwO", + Enabled = true, + CssUrl = "/css/theme.css", + DonateUrl = "https://ko-fi.com/masuowo" + }); + } + + return Task.FromResult(themes.ToArray()); + } + + public async Task GetEnabled() => + (await GetInstalled()) + .Where(x => x.Enabled) + .ToArray(); +} \ No newline at end of file diff --git a/Moonlight/Pages/_Host.cshtml b/Moonlight/Pages/_Host.cshtml index fb6551d..801ac5e 100644 --- a/Moonlight/Pages/_Host.cshtml +++ b/Moonlight/Pages/_Host.cshtml @@ -1,8 +1,15 @@ @page "/" @using Microsoft.AspNetCore.Components.Web +@using Moonlight.App.Services.Sys @namespace Moonlight.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@inject MoonlightThemeService MoonlightThemeService + +@{ + var themes = await MoonlightThemeService.GetEnabled(); +} + @@ -10,12 +17,17 @@ Moonlight - + - + - - + + @foreach (var theme in themes) + { + + + } + @@ -40,6 +52,16 @@ + +@foreach (var theme in themes) +{ + if (theme.JsUrl != null) + { + + + } +} + \ No newline at end of file diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index 62fe67a..c167441 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -19,7 +19,6 @@ using Moonlight.App.Services.Utils; using Serilog; var configService = new ConfigService(); -var moonlightService = new MoonlightService(configService); Directory.CreateDirectory(PathBuilder.Dir("storage")); Directory.CreateDirectory(PathBuilder.Dir("storage", "logs")); @@ -95,7 +94,8 @@ builder.Services.AddSingleton(configService); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -builder.Services.AddSingleton(moonlightService); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); @@ -112,7 +112,6 @@ var config = builder.Logging.AddConfiguration(config.Build()); var app = builder.Build(); -moonlightService.Application = app; app.UseStaticFiles(); app.UseRouting(); @@ -124,8 +123,10 @@ app.MapControllers(); // Auto start background services app.Services.GetRequiredService(); -var serviceService = app.Services.GetRequiredService(); +var moonlightService = app.Services.GetRequiredService(); +moonlightService.Application = app; +var serviceService = app.Services.GetRequiredService(); serviceService.Register(ServiceType.Server); await pluginService.RunPrePost(app); diff --git a/Moonlight/Shared/Components/Navigations/AdminSysNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminSysNavigation.razor index 13b1706..a3a730b 100644 --- a/Moonlight/Shared/Components/Navigations/AdminSysNavigation.razor +++ b/Moonlight/Shared/Components/Navigations/AdminSysNavigation.razor @@ -11,6 +11,11 @@ Settings + diff --git a/Moonlight/Shared/Views/Admin/Sys/Themes.razor b/Moonlight/Shared/Views/Admin/Sys/Themes.razor new file mode 100644 index 0000000..1fee349 --- /dev/null +++ b/Moonlight/Shared/Views/Admin/Sys/Themes.razor @@ -0,0 +1,72 @@ +@page "/admin/sys/themes" + +@using Moonlight.App.Extensions.Attributes +@using Moonlight.App.Models.Enums +@using Moonlight.App.Models.Forms.Admin.Sys.Themes +@using Moonlight.App.Repositories +@using Moonlight.App.Services.Sys +@using BlazorTable +@using Moonlight.App.Models.Abstractions + +@attribute [RequirePermission(Permission.AdminRoot)] + +@inject MoonlightThemeService MoonlightThemeService + + + +
+ + + + + + + + + + + + +
+ +@code +{ + private ApplicationTheme[] EnabledThemes; + + private Theme[] LoadData(Repository repository) + { + // No async => .Result needs to be used. :c + EnabledThemes = MoonlightThemeService.GetEnabled().Result; + + return repository.Get().ToArray(); + } + + private Task ExportTheme(Theme theme) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Moonlight/wwwroot/css/demotheme.css b/Moonlight/wwwroot/css/demotheme.css new file mode 100644 index 0000000..ffe881f --- /dev/null +++ b/Moonlight/wwwroot/css/demotheme.css @@ -0,0 +1,3 @@ +body { + background-color: #0d8ddc !important; +} \ No newline at end of file From c5a3c0550c1a35028555f3461ca5a501e5b2a6b6 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Fri, 22 Dec 2023 14:38:08 +0100 Subject: [PATCH 2/4] As the default theme is not shown in the Themes page, we do not need this way of checking if its enabled --- Moonlight/Shared/Views/Admin/Sys/Themes.razor | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Moonlight/Shared/Views/Admin/Sys/Themes.razor b/Moonlight/Shared/Views/Admin/Sys/Themes.razor index 1fee349..5e42f20 100644 --- a/Moonlight/Shared/Views/Admin/Sys/Themes.razor +++ b/Moonlight/Shared/Views/Admin/Sys/Themes.razor @@ -25,8 +25,7 @@