Skip to content

Commit e56c5ed

Browse files
authored
Merge pull request #453
Implemented theme importing and exporting
2 parents 6748288 + 70b310a commit e56c5ed

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Moonlight.Shared.Misc;
2+
3+
namespace Moonlight.Client.Models;
4+
5+
public class ThemeTransferModel
6+
{
7+
public string Name { get; set; }
8+
public string Author { get; set; }
9+
public string Version { get; set; }
10+
public string? UpdateUrl { get; set; }
11+
public string? DonateUrl { get; set; }
12+
13+
public ApplicationTheme Content { get; set; } = new();
14+
}

Moonlight.Client/UI/Views/Admin/Sys/Customisation/Index.razor

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
@page "/admin/system/customisation"
22

3+
@using System.Text.Json
34
@using Microsoft.AspNetCore.Authorization
45
@using MoonCore.Blazor.FlyonUi.DataTables
6+
@using MoonCore.Blazor.FlyonUi.Helpers
7+
@using MoonCore.Helpers
58
@using MoonCore.Models
9+
@using Moonlight.Client.Models
610
@using Moonlight.Client.Services
11+
@using Moonlight.Shared.Http.Requests.Admin.Sys.Theme
712
@using Moonlight.Shared.Http.Responses.Admin
813

914
@attribute [Authorize(Policy = "permissions:admin.system.theme")]
1015

1116
@inject ThemeService ThemeService
1217
@inject AlertService AlertService
1318
@inject ToastService ToastService
19+
@inject DownloadService DownloadService
20+
@inject ILogger<Index> Logger
1421

1522
<NavTabs Index="1" Names="UiConstants.AdminNavNames" Links="UiConstants.AdminNavLinks"/>
1623

@@ -24,13 +31,13 @@
2431
<i class="icon-file-up"></i>
2532
Import
2633
</label>
27-
<InputFile id="import-theme" class="hidden" />
34+
<InputFile OnChange="Import" id="import-theme" class="hidden" multiple />
2835
<a href="/admin/system/customisation/themes/create" class="btn btn-primary">Create</a>
2936
</div>
3037
</div>
3138

3239
<div class="my-2.5">
33-
<DataTable TItem="ThemeResponse">
40+
<DataTable @ref="Table" TItem="ThemeResponse">
3441
<Configuration>
3542
<DataTableColumn TItem="ThemeResponse" Field="@(x => x.Id)" Name="Id"/>
3643
<DataTableColumn TItem="ThemeResponse" Field="@(x => x.Name)" Name="Name">
@@ -66,6 +73,11 @@
6673
</a>
6774
}
6875

76+
<a @onclick="() => Export(context)" @onclick:preventDefault href="#" class="flex items-center mr-2 sm:mr-3">
77+
<i class="text-success icon-download me-1"></i>
78+
<span class="text-success">Export</span>
79+
</a>
80+
6981
<a href="/admin/system/customisation/themes/@(context.Id)" class="mr-2 sm:mr-3">
7082
<i class="icon-pencil text-primary"></i>
7183
</a>
@@ -92,6 +104,85 @@
92104

93105
private async Task<IPagedData<ThemeResponse>> LoadItems(PaginationOptions options)
94106
=> await ThemeService.Get(options.Page, options.PerPage);
107+
108+
private async Task Import(InputFileChangeEventArgs eventArgs)
109+
{
110+
if(eventArgs.FileCount < 1)
111+
return;
112+
113+
var files = eventArgs.GetMultipleFiles();
114+
115+
var maxFileSize = ByteConverter.FromMegaBytes(1).Bytes;
116+
117+
foreach (var file in files)
118+
{
119+
try
120+
{
121+
if (!file.Name.EndsWith(".json"))
122+
{
123+
await ToastService.Error($"Unable to import {file.Name}", "Only .json files are supported");
124+
continue;
125+
}
126+
127+
if (file.Size > maxFileSize)
128+
{
129+
await ToastService.Error($"Unable to import {file.Name}", "Exceeded the maximum file limit of 1MB");
130+
continue;
131+
}
132+
133+
await using var stream = file.OpenReadStream(maxFileSize);
134+
var themeTransfer = await JsonSerializer.DeserializeAsync<ThemeTransferModel>(stream);
135+
136+
stream.Close();
137+
138+
if (themeTransfer == null)
139+
{
140+
await ToastService.Error($"Unable to import {file.Name}", "Failed to deserialize the content");
141+
continue;
142+
}
143+
144+
var theme = await ThemeService.Create(new CreateThemeRequest()
145+
{
146+
Name = themeTransfer.Name,
147+
Author = themeTransfer.Author,
148+
Content = themeTransfer.Content,
149+
DonateUrl = themeTransfer.DonateUrl,
150+
UpdateUrl = themeTransfer.UpdateUrl,
151+
Version = themeTransfer.Version
152+
});
153+
154+
await ToastService.Success("Successfully imported theme", theme.Name);
155+
156+
await Table.Refresh();
157+
}
158+
catch (Exception e)
159+
{
160+
Logger.LogError(e, "An unhandled error occured while importing file {file} as theme", file.Name);
161+
}
162+
}
163+
}
164+
165+
private async Task Export(ThemeResponse theme)
166+
{
167+
var transfer = new ThemeTransferModel()
168+
{
169+
Name = theme.Name,
170+
Author = theme.Author,
171+
Content = theme.Content,
172+
DonateUrl = theme.DonateUrl,
173+
UpdateUrl = theme.UpdateUrl,
174+
Version = theme.Version
175+
};
176+
177+
var json = JsonSerializer.Serialize(transfer, new JsonSerializerOptions()
178+
{
179+
WriteIndented = true
180+
});
181+
182+
var fileName = $"{transfer.Name.Replace(" ", string.Empty).Trim()}.json";
183+
184+
await DownloadService.Download(fileName, json);
185+
}
95186

96187
private async Task Delete(ThemeResponse response)
97188
{

0 commit comments

Comments
 (0)