Implemented basic theme feature. Missing are import, export and asset proxy

This commit is contained in:
Marcel Baumgartner 2023-12-22 14:27:37 +01:00
parent f57aac4f6c
commit a67829035e
16 changed files with 1025 additions and 10 deletions

View file

@ -16,6 +16,12 @@ public class ConfigV1
[JsonProperty("Store")] public StoreData Store { get; set; } = new(); [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 public class StoreData
{ {
[JsonProperty("Currency")] [JsonProperty("Currency")]

View file

@ -34,6 +34,9 @@ public class DataContext : DbContext
// Tickets // Tickets
public DbSet<Ticket> Tickets { get; set; } public DbSet<Ticket> Tickets { get; set; }
public DbSet<TicketMessage> TicketMessages { get; set; } public DbSet<TicketMessage> TicketMessages { get; set; }
// Themes
public DbSet<Theme> Themes { get; set; }
public DataContext(ConfigService configService) public DataContext(ConfigService configService)
{ {

View file

@ -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;
}

View file

@ -0,0 +1,697 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AuthorId")
.HasColumnType("INTEGER");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("AuthorId");
b.ToTable("Posts");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AuthorId")
.HasColumnType("INTEGER");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<int?>("PostId")
.HasColumnType("INTEGER");
b.Property<DateTime>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<int?>("PostId")
.HasColumnType("INTEGER");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Filter")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("WordFilters");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Categories");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Coupon", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Amount")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Percent")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Coupons");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("CouponId")
.HasColumnType("INTEGER");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Amount")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<double>("Value")
.HasColumnType("REAL");
b.HasKey("Id");
b.ToTable("GiftCodes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCodeUse", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("GiftCodeId")
.HasColumnType("INTEGER");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("CategoryId")
.HasColumnType("INTEGER");
b.Property<string>("ConfigJson")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Duration")
.HasColumnType("INTEGER");
b.Property<int>("MaxPerUser")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<double>("Price")
.HasColumnType("REAL");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Stock")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Products");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ConfigJsonOverride")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Nickname")
.HasColumnType("TEXT");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER");
b.Property<int>("ProductId")
.HasColumnType("INTEGER");
b.Property<DateTime>("RenewAt")
.HasColumnType("TEXT");
b.Property<bool>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("ServiceId")
.HasColumnType("INTEGER");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("Price")
.HasColumnType("REAL");
b.Property<string>("Text")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Transaction");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Theme", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CssUrl")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DonateUrl")
.HasColumnType("TEXT");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER");
b.Property<string>("JsUrl")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Themes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.Ticket", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<int>("CreatorId")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("Open")
.HasColumnType("INTEGER");
b.Property<int>("Priority")
.HasColumnType("INTEGER");
b.Property<int?>("ServiceId")
.HasColumnType("INTEGER");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Attachment")
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<bool>("IsSupport")
.HasColumnType("INTEGER");
b.Property<int?>("SenderId")
.HasColumnType("INTEGER");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Avatar")
.HasColumnType("TEXT");
b.Property<double>("Balance")
.HasColumnType("REAL");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Flags")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Permissions")
.HasColumnType("INTEGER");
b.Property<DateTime>("TokenValidTimestamp")
.HasColumnType("TEXT");
b.Property<string>("TotpKey")
.HasColumnType("TEXT");
b.Property<string>("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
}
}
}

View file

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddThemeModel : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Themes",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: false),
Author = table.Column<string>(type: "TEXT", nullable: false),
DonateUrl = table.Column<string>(type: "TEXT", nullable: true),
CssUrl = table.Column<string>(type: "TEXT", nullable: false),
JsUrl = table.Column<string>(type: "TEXT", nullable: true),
Enabled = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Themes", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Themes");
}
}
}

View file

@ -357,6 +357,38 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("Transaction"); b.ToTable("Transaction");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.Theme", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CssUrl")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DonateUrl")
.HasColumnType("TEXT");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER");
b.Property<string>("JsUrl")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Themes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.Ticket", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Tickets.Ticket", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -7,11 +7,15 @@ namespace Moonlight.App.Services.Sys;
public class MoonlightService // This service can be used to perform strictly panel specific actions public class MoonlightService // This service can be used to perform strictly panel specific actions
{ {
private readonly ConfigService ConfigService; 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; ConfigService = configService;
ServiceProvider = serviceProvider;
} }
public async Task Restart() public async Task Restart()

View file

@ -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<ApplicationTheme[]> GetInstalled()
{
using var scope = ServiceProvider.CreateScope();
var themeRepo = scope.ServiceProvider.GetRequiredService<Repository<Theme>>();
var themes = new List<ApplicationTheme>();
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<ApplicationTheme[]> GetEnabled() =>
(await GetInstalled())
.Where(x => x.Enabled)
.ToArray();
}

View file

@ -1,8 +1,15 @@
@page "/" @page "/"
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using Moonlight.App.Services.Sys
@namespace Moonlight.Pages @namespace Moonlight.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@inject MoonlightThemeService MoonlightThemeService
@{
var themes = await MoonlightThemeService.GetEnabled();
}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-bs-theme="dark"> <html lang="en" data-bs-theme="dark">
<head> <head>
@ -10,12 +17,17 @@
<base href="~/"/> <base href="~/"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Moonlight</title> <title>Moonlight</title>
<link rel="shortcut icon" href="/img/logo.svg"> <link rel="shortcut icon" href="/img/logo.svg">
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/> <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
<link href="/css/theme.css" rel="stylesheet" type="text/css"/> @foreach (var theme in themes)
{
<!-- Theme: @(theme.Name) by @(theme.Author) -->
<link href="@(theme.CssUrl)" rel="stylesheet" type="text/css"/>
}
<link href="/css/utils.css" rel="stylesheet" type="text/css"/> <link href="/css/utils.css" rel="stylesheet" type="text/css"/>
<link href="/css/blazor.css" rel="stylesheet" type="text/css"/> <link href="/css/blazor.css" rel="stylesheet" type="text/css"/>
<link href="/css/boxicons.min.css" rel="stylesheet" type="text/css"/> <link href="/css/boxicons.min.css" rel="stylesheet" type="text/css"/>
@ -40,6 +52,16 @@
<script src="/_content/BlazorTable/BlazorTable.min.js"></script> <script src="/_content/BlazorTable/BlazorTable.min.js"></script>
<script src="/js/sweetalert2.js"></script> <script src="/js/sweetalert2.js"></script>
<script src="/js/ckeditor.js"></script> <script src="/js/ckeditor.js"></script>
@foreach (var theme in themes)
{
if (theme.JsUrl != null)
{
<!-- Theme: @(theme.Name) by @(theme.Author) -->
<script src="@(theme.JsUrl)"></script>
}
}
<script src="/_framework/blazor.server.js"></script> <script src="/_framework/blazor.server.js"></script>
</body> </body>
</html> </html>

View file

@ -19,7 +19,6 @@ using Moonlight.App.Services.Utils;
using Serilog; using Serilog;
var configService = new ConfigService(); var configService = new ConfigService();
var moonlightService = new MoonlightService(configService);
Directory.CreateDirectory(PathBuilder.Dir("storage")); Directory.CreateDirectory(PathBuilder.Dir("storage"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs")); Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
@ -95,7 +94,8 @@ builder.Services.AddSingleton(configService);
builder.Services.AddSingleton<SessionService>(); builder.Services.AddSingleton<SessionService>();
builder.Services.AddSingleton<BucketService>(); builder.Services.AddSingleton<BucketService>();
builder.Services.AddSingleton<MailService>(); builder.Services.AddSingleton<MailService>();
builder.Services.AddSingleton(moonlightService); builder.Services.AddSingleton<MoonlightService>();
builder.Services.AddSingleton<MoonlightThemeService>();
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor();
@ -112,7 +112,6 @@ var config =
builder.Logging.AddConfiguration(config.Build()); builder.Logging.AddConfiguration(config.Build());
var app = builder.Build(); var app = builder.Build();
moonlightService.Application = app;
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
@ -124,8 +123,10 @@ app.MapControllers();
// Auto start background services // Auto start background services
app.Services.GetRequiredService<AutoMailSendService>(); app.Services.GetRequiredService<AutoMailSendService>();
var serviceService = app.Services.GetRequiredService<ServiceDefinitionService>(); var moonlightService = app.Services.GetRequiredService<MoonlightService>();
moonlightService.Application = app;
var serviceService = app.Services.GetRequiredService<ServiceDefinitionService>();
serviceService.Register<DummyServiceDefinition>(ServiceType.Server); serviceService.Register<DummyServiceDefinition>(ServiceType.Server);
await pluginService.RunPrePost(app); await pluginService.RunPrePost(app);

View file

@ -11,6 +11,11 @@
<i class="bx bx-sm bx-cog me-2"></i> Settings <i class="bx bx-sm bx-cog me-2"></i> Settings
</a> </a>
</li> </li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/sys/themes">
<i class="bx bx-sm bx-palette me-2"></i> Themes
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -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
<AdminSysNavigation Index="2"/>
<div class="mt-5">
<AutoCrud TItem="Theme"
TCreateForm="AddThemeForm"
TUpdateForm="EditThemeForm"
Title="Manage themes"
Load="LoadData">
<Column TableItem="Theme" Title="Id" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="Theme" Title="Name" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
<Column TableItem="Theme" Title="Author" Field="@(x => x.Author)" Sortable="true" Filterable="true"/>
<Column TableItem="Theme" Field="@(x => x.Id)" Title="" Filterable="false" Sortable="false">
<Template>
@if (EnabledThemes
.Any(x => x.Name == context.Name && x.Author == context.Author))
{
<span class="text-success">Enabled</span>
}
else
{
<span class="text-muted">Disabled</span>
}
</Template>
</Column>
<Column TableItem="Theme" Field="@(x => x.Id)" Title="" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<div class="btn-group">
@if (context.DonateUrl != null)
{
<a class="btn btn-sm btn-info" href="@(context.DonateUrl)" target="_blank">Donate</a>
}
<WButton OnClick="() => ExportTheme(context)" Text="Export" CssClasses="btn btn-sm btn-secondary"/>
</div>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
</AutoCrud>
</div>
@code
{
private ApplicationTheme[] EnabledThemes;
private Theme[] LoadData(Repository<Theme> 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;
}
}

3
Moonlight/wwwroot/css/demotheme.css vendored Normal file
View file

@ -0,0 +1,3 @@
body {
background-color: #0d8ddc !important;
}