Merge pull request #345 from Moonlight-Panel/v2_AddThemeFeature
Added theme feature
This commit is contained in:
commit
0d8cc5bd5d
27 changed files with 1568 additions and 26 deletions
|
@ -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")]
|
||||
|
|
|
@ -34,6 +34,9 @@ public class DataContext : DbContext
|
|||
// Tickets
|
||||
public DbSet<Ticket> Tickets { get; set; }
|
||||
public DbSet<TicketMessage> TicketMessages { get; set; }
|
||||
|
||||
// Themes
|
||||
public DbSet<Theme> Themes { get; set; }
|
||||
|
||||
public DataContext(ConfigService configService)
|
||||
{
|
||||
|
|
13
Moonlight/App/Database/Entities/Theme.cs
Normal file
13
Moonlight/App/Database/Entities/Theme.cs
Normal 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;
|
||||
}
|
697
Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.Designer.cs
generated
Normal file
697
Moonlight/App/Database/Migrations/20231222100225_AddThemeModel.Designer.cs
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -357,6 +357,38 @@ namespace Moonlight.App.Database.Migrations
|
|||
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")
|
||||
|
|
66
Moonlight/App/Http/Controllers/Api/AssetProxyController.cs
Normal file
66
Moonlight/App/Http/Controllers/Api/AssetProxyController.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Services.Sys;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/assetproxy")]
|
||||
public class AssetProxyController : Controller
|
||||
{
|
||||
private readonly MoonlightThemeService ThemeService;
|
||||
|
||||
public AssetProxyController(MoonlightThemeService themeService)
|
||||
{
|
||||
ThemeService = themeService;
|
||||
}
|
||||
|
||||
[HttpGet("theme/{id}/js")]
|
||||
public async Task<ActionResult> GetThemeJs(int id)
|
||||
{
|
||||
var enabledThemes = await ThemeService.GetEnabled();
|
||||
var selectedTheme = enabledThemes.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (selectedTheme == null)
|
||||
return NotFound();
|
||||
|
||||
try
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
var content = await httpClient.GetByteArrayAsync(selectedTheme.JsUrl);
|
||||
|
||||
return File(content, "text/javascript");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn($"Error proxying js for theme {id}");
|
||||
Logger.Warn(e);
|
||||
return Problem();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("theme/{id}/css")]
|
||||
public async Task<ActionResult> GetThemeCss(int id)
|
||||
{
|
||||
var enabledThemes = await ThemeService.GetEnabled();
|
||||
var selectedTheme = enabledThemes.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (selectedTheme == null)
|
||||
return NotFound();
|
||||
|
||||
try
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
var content = await httpClient.GetByteArrayAsync(selectedTheme.CssUrl);
|
||||
|
||||
return File(content, "text/css");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn($"Error proxying css for theme {id}");
|
||||
Logger.Warn(e);
|
||||
return Problem();
|
||||
}
|
||||
}
|
||||
}
|
13
Moonlight/App/Models/Abstractions/ApplicationTheme.cs
Normal file
13
Moonlight/App/Models/Abstractions/ApplicationTheme.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Moonlight.App.Models.Abstractions;
|
||||
|
||||
public class ApplicationTheme
|
||||
{
|
||||
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;
|
||||
}
|
23
Moonlight/App/Models/Forms/Admin/Sys/Themes/AddThemeForm.cs
Normal file
23
Moonlight/App/Models/Forms/Admin/Sys/Themes/AddThemeForm.cs
Normal 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;
|
||||
}
|
26
Moonlight/App/Models/Forms/Admin/Sys/Themes/EditThemeForm.cs
Normal file
26
Moonlight/App/Models/Forms/Admin/Sys/Themes/EditThemeForm.cs
Normal 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;
|
||||
}
|
10
Moonlight/App/Models/Json/Theme/ThemeExport.cs
Normal file
10
Moonlight/App/Models/Json/Theme/ThemeExport.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Moonlight.App.Models.Json.Theme;
|
||||
|
||||
public class ThemeExport
|
||||
{
|
||||
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; } = "";
|
||||
}
|
10
Moonlight/App/Models/Json/Theme/ThemeImport.cs
Normal file
10
Moonlight/App/Models/Json/Theme/ThemeImport.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Moonlight.App.Models.Json.Theme;
|
||||
|
||||
public class ThemeImport
|
||||
{
|
||||
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; } = "";
|
||||
}
|
34
Moonlight/App/Services/Interop/FileDownloadService.cs
Normal file
34
Moonlight/App/Services/Interop/FileDownloadService.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Text;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Moonlight.App.Services.Interop;
|
||||
|
||||
public class FileDownloadService
|
||||
{
|
||||
private readonly IJSRuntime JsRuntime;
|
||||
|
||||
public FileDownloadService(IJSRuntime jsRuntime)
|
||||
{
|
||||
JsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task DownloadStream(string fileName, Stream stream)
|
||||
{
|
||||
using var streamRef = new DotNetStreamReference(stream);
|
||||
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.utils.download", fileName, streamRef);
|
||||
}
|
||||
|
||||
public async Task DownloadBytes(string fileName, byte[] bytes)
|
||||
{
|
||||
var ms = new MemoryStream(bytes);
|
||||
|
||||
await DownloadStream(fileName, ms);
|
||||
|
||||
ms.Close();
|
||||
await ms.DisposeAsync();
|
||||
}
|
||||
|
||||
public async Task DownloadString(string fileName, string content) =>
|
||||
await DownloadBytes(fileName, Encoding.UTF8.GetBytes(content));
|
||||
}
|
|
@ -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()
|
||||
|
|
51
Moonlight/App/Services/Sys/MoonlightThemeService.cs
Normal file
51
Moonlight/App/Services/Sys/MoonlightThemeService.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
using Mappy.Net;
|
||||
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 => Mapper.Map<ApplicationTheme>(x)));
|
||||
|
||||
if (ConfigService.Get().Theme.EnableDefault)
|
||||
{
|
||||
themes.Insert(0, new()
|
||||
{
|
||||
Id = 0,
|
||||
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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
|
@ -10,17 +17,25 @@
|
|||
<base href="~/"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Moonlight</title>
|
||||
|
||||
|
||||
<link rel="shortcut icon" href="/img/logo.svg">
|
||||
|
||||
|
||||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
|
||||
|
||||
<link href="/css/theme.css" rel="stylesheet" type="text/css"/>
|
||||
|
||||
@foreach (var theme in themes)
|
||||
{
|
||||
var finalUrl = theme.CssUrl.StartsWith("/") ? theme.CssUrl : $"/api/assetproxy/theme/{theme.Id}/css";
|
||||
|
||||
<!-- Theme: @(theme.Name) by @(theme.Author) -->
|
||||
<link href="@(finalUrl)" 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/boxicons.min.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="/css/sweetalert2dark.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="https://fonts.googleapis.com/css?family=Inter:300,400,500,600,700" rel="stylesheet" type="text/css">
|
||||
<link href="/css/interfont.css" rel="stylesheet" type="text/css"/>
|
||||
@* <link href="https://fonts.googleapis.com/css?family=Inter:300,400,500,600,700" rel="stylesheet" type="text/css"> *@
|
||||
</head>
|
||||
<body data-kt-app-header-fixed="true"
|
||||
data-kt-app-header-fixed-mobile="true"
|
||||
|
@ -40,6 +55,18 @@
|
|||
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
||||
<script src="/js/sweetalert2.js"></script>
|
||||
<script src="/js/ckeditor.js"></script>
|
||||
|
||||
@foreach (var theme in themes)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(theme.JsUrl))
|
||||
{
|
||||
var finalUrl = theme.JsUrl.StartsWith("/") ? theme.JsUrl : $"/api/assetproxy/theme/{theme.Id}/js";
|
||||
|
||||
<!-- Theme: @(theme.Name) by @(theme.Author) -->
|
||||
<script src="@(finalUrl)"></script>
|
||||
}
|
||||
}
|
||||
|
||||
<script src="/_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -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"));
|
||||
|
@ -57,6 +56,7 @@ builder.Services.AddScoped<CookieService>();
|
|||
builder.Services.AddScoped<ToastService>();
|
||||
builder.Services.AddScoped<ModalService>();
|
||||
builder.Services.AddScoped<AlertService>();
|
||||
builder.Services.AddScoped<FileDownloadService>();
|
||||
|
||||
// Services / Store
|
||||
builder.Services.AddScoped<StoreService>();
|
||||
|
@ -95,7 +95,8 @@ builder.Services.AddSingleton(configService);
|
|||
builder.Services.AddSingleton<SessionService>();
|
||||
builder.Services.AddSingleton<BucketService>();
|
||||
builder.Services.AddSingleton<MailService>();
|
||||
builder.Services.AddSingleton(moonlightService);
|
||||
builder.Services.AddSingleton<MoonlightService>();
|
||||
builder.Services.AddSingleton<MoonlightThemeService>();
|
||||
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddServerSideBlazor();
|
||||
|
@ -112,7 +113,6 @@ var config =
|
|||
builder.Logging.AddConfiguration(config.Build());
|
||||
|
||||
var app = builder.Build();
|
||||
moonlightService.Application = app;
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
@ -124,8 +124,10 @@ app.MapControllers();
|
|||
// Auto start background services
|
||||
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);
|
||||
|
||||
await pluginService.RunPrePost(app);
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
<div class="card-header">
|
||||
<h3 class="card-title">@(Title)</h3>
|
||||
<div class="card-toolbar">
|
||||
<button @onclick="StartCreate" class="btn btn-icon btn-success">
|
||||
@Toolbar
|
||||
<button @onclick="StartCreate" class="btn btn-icon btn-success ms-3">
|
||||
<i class="bx bx-sm bx-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -26,7 +27,7 @@
|
|||
PageSize="50"
|
||||
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
|
||||
TableHeadClass="fw-bold text-muted">
|
||||
@ChildContent
|
||||
@View
|
||||
<Column TableItem="TItem" Field="IdExpression" Title="" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<div class="text-end">
|
||||
|
@ -109,7 +110,10 @@
|
|||
public Func<Repository<TItem>, TItem[]> Load { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
public RenderFragment View { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment Toolbar { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? ValidateAdd { get; set; }
|
||||
|
@ -144,6 +148,8 @@
|
|||
IdExpression = CreateExpression();
|
||||
}
|
||||
|
||||
public async Task Reload() => await LazyLoader.Reload();
|
||||
|
||||
private Task LoadItems(LazyLoader _)
|
||||
{
|
||||
Items = Load.Invoke(ItemRepository);
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
@inject ToastService ToastService
|
||||
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden=""/>
|
||||
<label for="fileUpload" class="">
|
||||
@if (SelectedFile == null)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
</label>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public IBrowserFile? SelectedFile { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int MaxFileSize { get; set; } = 1024 * 1024 * 5;
|
||||
|
||||
[Parameter]
|
||||
public Func<IBrowserFile, Task>? OnFileSelected { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||
{
|
||||
if (arg.FileCount > 0)
|
||||
{
|
||||
if (arg.File.Size < MaxFileSize)
|
||||
{
|
||||
SelectedFile = arg.File;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if(OnFileSelected != null)
|
||||
await OnFileSelected.Invoke(SelectedFile);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await ToastService.Danger($"The uploaded file should not be bigger than {Formatter.FormatSize(MaxFileSize)}");
|
||||
}
|
||||
|
||||
SelectedFile = null;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task RemoveSelection()
|
||||
{
|
||||
SelectedFile = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,11 @@
|
|||
<i class="bx bx-sm bx-cog me-2"></i> Settings
|
||||
</a>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,8 +22,10 @@
|
|||
TUpdateForm="EditWordFilter"
|
||||
Title="Manage word filter"
|
||||
Load="LoadData">
|
||||
<Column TableItem="WordFilter" Field="@(x => x.Id)" Title="Id" Sortable="false" Filterable="true"/>
|
||||
<Column TableItem="WordFilter" Field="@(x => x.Filter)" Title="Filter" Sortable="false" Filterable="true"/>
|
||||
<View>
|
||||
<Column TableItem="WordFilter" Field="@(x => x.Id)" Title="Id" Sortable="false" Filterable="true"/>
|
||||
<Column TableItem="WordFilter" Field="@(x => x.Filter)" Title="Filter" Sortable="false" Filterable="true"/>
|
||||
</View>
|
||||
</AutoCrud>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
Load="LoadData"
|
||||
ValidateAdd="Validate"
|
||||
ValidateUpdate="Validate">
|
||||
<Column TableItem="Coupon" Title="Id" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Code" Field="@(x => x.Code)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Amount" Field="@(x => x.Amount)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Percent" Field="@(x => x.Percent)" Sortable="true" Filterable="true"/>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
|
||||
<View>
|
||||
<Column TableItem="Coupon" Title="Id" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Code" Field="@(x => x.Code)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Amount" Field="@(x => x.Amount)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Percent" Field="@(x => x.Percent)" Sortable="true" Filterable="true"/>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
|
||||
</View>
|
||||
</AutoCrud>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -20,10 +20,12 @@
|
|||
Title="Manage gift codes"
|
||||
Load="LoadData"
|
||||
ValidateAdd="Validate">
|
||||
<Column TableItem="GiftCode" Field="@(x => x.Code)" Title="Code" Sortable="false" Filterable="true" />
|
||||
<Column TableItem="GiftCode" Field="@(x => x.Amount)" Title="Amount" Sortable="true" Filterable="true" />
|
||||
<Column TableItem="GiftCode" Field="@(x => x.Value)" Title="Value" Sortable="true" Filterable="true" />
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
|
||||
<View>
|
||||
<Column TableItem="GiftCode" Field="@(x => x.Code)" Title="Code" Sortable="false" Filterable="true"/>
|
||||
<Column TableItem="GiftCode" Field="@(x => x.Amount)" Title="Amount" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="GiftCode" Field="@(x => x.Value)" Title="Value" Sortable="true" Filterable="true"/>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
|
||||
</View>
|
||||
</AutoCrud>
|
||||
</div>
|
||||
|
||||
|
@ -40,7 +42,7 @@
|
|||
{
|
||||
if (GiftCodeRepository.Get().Any(x => x.Code == giftCode.Code && x.Id != giftCode.Id))
|
||||
throw new DisplayException("A gift code with that code does already exist");
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
120
Moonlight/Shared/Views/Admin/Sys/Themes.razor
Normal file
120
Moonlight/Shared/Views/Admin/Sys/Themes.razor
Normal file
|
@ -0,0 +1,120 @@
|
|||
@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 BlazorTable
|
||||
@using Mappy.Net
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Moonlight.App.Models.Json.Theme
|
||||
@using Newtonsoft.Json
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminRoot)]
|
||||
|
||||
@inject ToastService ToastService
|
||||
@inject Repository<Theme> ThemeRepository
|
||||
@inject FileDownloadService DownloadService
|
||||
|
||||
<AdminSysNavigation Index="2"/>
|
||||
|
||||
<div class="mt-5">
|
||||
<AutoCrud TItem="Theme"
|
||||
TCreateForm="AddThemeForm"
|
||||
TUpdateForm="EditThemeForm"
|
||||
Title="Manage themes"
|
||||
Load="LoadData">
|
||||
<Toolbar>
|
||||
<SmartCustomFileSelect @ref="ThemeFileSelect" OnFileSelected="ImportTheme">
|
||||
<div class="btn btn-secondary">
|
||||
<i class="bx bx-sm bx-upload me-3"></i>
|
||||
Import theme
|
||||
</div>
|
||||
</SmartCustomFileSelect>
|
||||
</Toolbar>
|
||||
<View>
|
||||
<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 (context.Enabled)
|
||||
{
|
||||
<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 (!string.IsNullOrEmpty(context.DonateUrl))
|
||||
{
|
||||
<a class="btn btn-info" href="@(context.DonateUrl)" target="_blank">Donate</a>
|
||||
}
|
||||
<WButton OnClick="() => ExportTheme(context)" Text="Export" CssClasses="btn btn-secondary"/>
|
||||
</div>
|
||||
</div>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
|
||||
</View>
|
||||
</AutoCrud>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private SmartCustomFileSelect ThemeFileSelect;
|
||||
private AutoCrud<Theme, AddThemeForm, EditThemeForm> AutoCrud;
|
||||
|
||||
private Theme[] LoadData(Repository<Theme> repository)
|
||||
{
|
||||
return repository.Get().ToArray();
|
||||
}
|
||||
|
||||
private async Task ExportTheme(Theme theme)
|
||||
{
|
||||
var model = Mapper.Map<ThemeExport>(theme);
|
||||
|
||||
var json = JsonConvert.SerializeObject(model, Formatting.Indented);
|
||||
|
||||
await ToastService.Info("Starting image download");
|
||||
await DownloadService.DownloadString($"{model.Name}.json", json);
|
||||
}
|
||||
|
||||
private async Task ImportTheme(IBrowserFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file.ContentType != "application/json")
|
||||
throw new DisplayException("Unknown file type. Only .json is supported");
|
||||
|
||||
var stream = file.OpenReadStream();
|
||||
var streamReader = new StreamReader(stream);
|
||||
var text = await streamReader.ReadToEndAsync();
|
||||
var theme = JsonConvert.DeserializeObject<ThemeImport>(text);
|
||||
|
||||
if (theme == null)
|
||||
throw new DisplayException("Unable to parse theme json");
|
||||
|
||||
var themeDb = Mapper.Map<Theme>(theme);
|
||||
ThemeRepository.Add(themeDb);
|
||||
|
||||
await ToastService.Success($"Successfully imported theme '{theme.Name}'");
|
||||
|
||||
await AutoCrud.Reload();
|
||||
}
|
||||
catch (DisplayException e)
|
||||
{
|
||||
await ToastService.Danger(e.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ThemeFileSelect.RemoveSelection();
|
||||
}
|
||||
}
|
||||
}
|
280
Moonlight/wwwroot/css/interfont.css
vendored
Normal file
280
Moonlight/wwwroot/css/interfont.css
vendored
Normal file
|
@ -0,0 +1,280 @@
|
|||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url() format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url() format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url() format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url() format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/fonts/Inter.woff2") format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
BIN
Moonlight/wwwroot/fonts/Inter.woff2
vendored
Normal file
BIN
Moonlight/wwwroot/fonts/Inter.woff2
vendored
Normal file
Binary file not shown.
13
Moonlight/wwwroot/js/moonlight.js
vendored
13
Moonlight/wwwroot/js/moonlight.js
vendored
|
@ -109,6 +109,19 @@ window.moonlight = {
|
|||
return text;
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
download: async function (fileName, contentStreamReference) {
|
||||
const arrayBuffer = await contentStreamReference.arrayBuffer();
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchorElement = document.createElement('a');
|
||||
anchorElement.href = url;
|
||||
anchorElement.download = fileName ?? '';
|
||||
anchorElement.click();
|
||||
anchorElement.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
},
|
||||
textEditor: {
|
||||
create: function(id)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue