mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
Improve User Interface
* File and folder pickers * Menu
This commit is contained in:
parent
069f6711f0
commit
48a165a923
@ -3,6 +3,7 @@
|
||||
namespace AssetRipper.GUI.Web;
|
||||
|
||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||
[JsonSerializable(typeof(string[]))]
|
||||
internal partial class AppJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetRipper.Text.Html" Version="1.0.0" />
|
||||
<PackageReference Include="NativeFileDialogs.Net" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using AssetRipper.Web.Content;
|
||||
using AssetRipper.GUI.Web.Paths;
|
||||
using AssetRipper.Web.Content;
|
||||
|
||||
namespace AssetRipper.GUI.Web;
|
||||
public abstract class DefaultPage : HtmlPage
|
||||
@ -6,7 +7,7 @@ public abstract class DefaultPage : HtmlPage
|
||||
public sealed override void Write(TextWriter writer)
|
||||
{
|
||||
base.Write(writer);
|
||||
using (new Html(writer).WithLang(Localizations.Localization.CurrentLanguageCode).End())
|
||||
using (new Html(writer).WithLang(Localization.CurrentLanguageCode).End())
|
||||
{
|
||||
using (new Head(writer).End())
|
||||
{
|
||||
@ -20,37 +21,83 @@ public abstract class DefaultPage : HtmlPage
|
||||
{
|
||||
using (new Header(writer).End())
|
||||
{
|
||||
using (new Nav(writer).WithClass("navbar navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3").End())
|
||||
using (new Div(writer).WithClass("btn-group").End())
|
||||
{
|
||||
using (new Div(writer).WithClass("container").End())
|
||||
using (new Div(writer).WithClass("btn-group dropdown").End())
|
||||
{
|
||||
new A(writer).WithClass("navbar-brand").WithHref("/").Close(GameFileLoader.Premium ? Localization.AssetRipperPremium : Localization.AssetRipperFree);
|
||||
using (new Button(writer)
|
||||
.WithClass("navbar-toggler")
|
||||
.WithType("button")
|
||||
.WithCustomAttribute("data-bs-toggle", "collapse")
|
||||
.WithCustomAttribute("data-bs-target", ".navbar-collapse")
|
||||
.WithCustomAttribute("aria-controls", "navbarSupportedContent")
|
||||
.WithCustomAttribute("aria-expanded", "false")
|
||||
.WithCustomAttribute("aria-label", "Toggle navigation").End())
|
||||
WriteDropdownButton(writer, Localization.MenuFile);
|
||||
using (new Ul(writer).WithClass("dropdown-menu").End())
|
||||
{
|
||||
new Span(writer).WithClass("navbar-toggler-icon").Close();
|
||||
}
|
||||
using (new Div(writer).WithClass("navbar-collapse collapse d-sm-inline-flex justify-content-between").End())
|
||||
{
|
||||
using (new Ul(writer).WithClass("navbar-nav flex-grow-1").End())
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
using (new Li(writer).WithClass("nav-item").End())
|
||||
WritePostLink(writer, "/LoadFile", Localization.MenuFileOpenFile, "dropdown-item");
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
WritePostLink(writer, "/LoadFolder", Localization.MenuFileOpenFolder, "dropdown-item");
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
WritePostLink(writer, "/Reset", Localization.MenuFileReset, "dropdown-item");
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new Hr(writer).WithClass("dropdown-divider").Close();
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new A(writer).WithClass("dropdown-item").WithHref("/Settings/Edit").Close(Localization.Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new Div(writer).WithClass("btn-group dropdown").End())
|
||||
{
|
||||
WriteDropdownButton(writer, "View");
|
||||
using (new Ul(writer).WithClass("dropdown-menu").End())
|
||||
{
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new A(writer).WithClass("dropdown-item").WithHref("/").Close(Localization.Home);
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new A(writer).WithClass("dropdown-item").WithHref("/Settings/Edit").Close(Localization.Settings);
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new A(writer).WithClass("dropdown-item").WithHref("/Commands").Close(Localization.Commands);
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new A(writer).WithClass("dropdown-item").WithHref("/Privacy").Close(Localization.Privacy);
|
||||
}
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new A(writer).WithClass("dropdown-item").WithHref("/Licenses").Close(Localization.Licenses);
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new Div(writer).WithClass("btn-group dropdown").End())
|
||||
{
|
||||
WriteDropdownButton(writer, Localization.MenuExport);
|
||||
using (new Ul(writer).WithClass("dropdown-menu").End())
|
||||
{
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
WritePostLink(writer, "/Export", Localization.MenuExportAll, "dropdown-item");
|
||||
}
|
||||
}
|
||||
}
|
||||
using (new Div(writer).WithClass("btn-group dropdown").End())
|
||||
{
|
||||
WriteDropdownButton(writer, Localization.MenuLanguage);
|
||||
using (new Ul(writer).WithClass("dropdown-menu").End())
|
||||
{
|
||||
foreach ((string code, string name) in Localizations.LocalizationLoader.LanguageNameDictionary)
|
||||
{
|
||||
using (new Li(writer).End())
|
||||
{
|
||||
new A(writer).WithClass("nav-link").WithHref("/").Close(Localization.Home);
|
||||
}
|
||||
using (new Li(writer).WithClass("nav-item").End())
|
||||
{
|
||||
new A(writer).WithClass("nav-link").WithHref("/Settings/Edit").Close("Settings");
|
||||
}
|
||||
using (new Li(writer).WithClass("nav-item").End())
|
||||
{
|
||||
new A(writer).WithClass("nav-link").WithHref("/Commands").Close("Commands");
|
||||
WritePostLink(writer, $"/Localization?code={code}", name, "dropdown-item");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,6 +128,23 @@ public abstract class DefaultPage : HtmlPage
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteDropdownButton(TextWriter writer, string buttonText)
|
||||
{
|
||||
new Button(writer).WithClass("btn btn-dark dropdown-toggle mx-0")
|
||||
.WithType("button")
|
||||
.WithCustomAttribute("data-bs-toggle", "dropdown")
|
||||
.WithCustomAttribute("aria-expanded", "false")
|
||||
.Close(buttonText);
|
||||
}
|
||||
|
||||
private static void WritePostLink(TextWriter writer, string url, string name, string? @class = null)
|
||||
{
|
||||
using (new Form(writer).WithAction(url).WithMethod("post").End())
|
||||
{
|
||||
new Input(writer).WithType("submit").WithClass(@class).WithValue(name.ToHtml()).Close();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract string? GetTitle();
|
||||
|
||||
public abstract void WriteInnerContent(TextWriter writer);
|
||||
|
||||
86
Source/AssetRipper.GUI.Web/Dialogs.cs
Normal file
86
Source/AssetRipper.GUI.Web/Dialogs.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using AssetRipper.Web.Extensions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NativeFileDialogs.Net;
|
||||
|
||||
namespace AssetRipper.GUI.Web;
|
||||
|
||||
internal static class Dialogs
|
||||
{
|
||||
private static readonly object lockObject = new();
|
||||
|
||||
public static class OpenFiles
|
||||
{
|
||||
public static Task HandleGetRequest(HttpContext context)
|
||||
{
|
||||
context.Response.DisableCaching();
|
||||
NfdStatus status = GetUserInput(out string[]? paths);
|
||||
//Maybe do something else when user cancels the dialog?
|
||||
return Results.Json(paths ?? [], AppJsonSerializerContext.Default.StringArray).ExecuteAsync(context);
|
||||
}
|
||||
|
||||
public static NfdStatus GetUserInput(out string[]? paths, IDictionary<string, string>? filters = null, string? defaultPath = null)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return Nfd.OpenDialogMultiple(out paths, filters, defaultPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpenFile
|
||||
{
|
||||
public static Task HandleGetRequest(HttpContext context)
|
||||
{
|
||||
context.Response.DisableCaching();
|
||||
NfdStatus status = GetUserInput(out string? path);
|
||||
//Maybe do something else when user cancels the dialog?
|
||||
return Results.Json(path ?? "", AppJsonSerializerContext.Default.String).ExecuteAsync(context);
|
||||
}
|
||||
|
||||
public static NfdStatus GetUserInput(out string? path, IDictionary<string, string>? filters = null, string? defaultPath = null)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return Nfd.OpenDialog(out path, filters, defaultPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpenFolder
|
||||
{
|
||||
public static Task HandleGetRequest(HttpContext context)
|
||||
{
|
||||
context.Response.DisableCaching();
|
||||
NfdStatus status = GetUserInput(out string? path);
|
||||
//Maybe do something else when user cancels the dialog?
|
||||
return Results.Json(path ?? "", AppJsonSerializerContext.Default.String).ExecuteAsync(context);
|
||||
}
|
||||
|
||||
public static NfdStatus GetUserInput(out string? path, string? defaultPath = null)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return Nfd.PickFolder(out path, defaultPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveFile
|
||||
{
|
||||
public static Task HandleGetRequest(HttpContext context)
|
||||
{
|
||||
context.Response.DisableCaching();
|
||||
NfdStatus status = GetUserInput(out string? path);
|
||||
//Maybe do something else when user cancels the dialog?
|
||||
return Results.Json(path ?? "", AppJsonSerializerContext.Default.String).ExecuteAsync(context);
|
||||
}
|
||||
|
||||
public static NfdStatus GetUserInput(out string? path, IDictionary<string, string>? filters = null, string defaultName = "Untitled", string? defaultPath = null)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return Nfd.SaveDialog(out path, filters, defaultName, defaultPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,76 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace AssetRipper.GUI.Web.Pages;
|
||||
|
||||
public static class Commands
|
||||
{
|
||||
public readonly struct Load : ICommand
|
||||
public readonly struct LoadFile : ICommand
|
||||
{
|
||||
static Task ICommand.Start(HttpRequest request)
|
||||
static async Task ICommand.Start(HttpRequest request)
|
||||
{
|
||||
string? path = request.Form["Path"];
|
||||
IFormCollection form = await request.ReadFormAsync();
|
||||
|
||||
string[]? paths;
|
||||
if (form.TryGetValue("Path", out StringValues values))
|
||||
{
|
||||
paths = values;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialogs.OpenFiles.GetUserInput(out paths);
|
||||
}
|
||||
|
||||
if (paths is { Length: > 0 })
|
||||
{
|
||||
GameFileLoader.LoadAndProcess(paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct LoadFolder : ICommand
|
||||
{
|
||||
static async Task ICommand.Start(HttpRequest request)
|
||||
{
|
||||
IFormCollection form = await request.ReadFormAsync();
|
||||
|
||||
string? path;
|
||||
if (form.TryGetValue("Path", out StringValues values))
|
||||
{
|
||||
path = values;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialogs.OpenFolder.GetUserInput(out path);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
GameFileLoader.LoadAndProcess([path]);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Export : ICommand
|
||||
{
|
||||
static Task ICommand.Start(HttpRequest request)
|
||||
static async Task ICommand.Start(HttpRequest request)
|
||||
{
|
||||
string? path = request.Form["Path"];
|
||||
IFormCollection form = await request.ReadFormAsync();
|
||||
|
||||
string? path;
|
||||
if (form.TryGetValue("Path", out StringValues values))
|
||||
{
|
||||
path = values;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialogs.OpenFolder.GetUserInput(out path);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
GameFileLoader.Export(path);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ public sealed class CommandsPage : DefaultPage
|
||||
{
|
||||
using (new P(writer).End())
|
||||
{
|
||||
WritePicker(writer, "/Load", Localization.MenuLoad, "btn btn-primary");
|
||||
WritePicker(writer, "/LoadFile", Localization.MenuLoad, "btn btn-primary");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ public sealed class IndexPage : DefaultPage
|
||||
{
|
||||
public static IndexPage Instance { get; } = new();
|
||||
|
||||
public override string? GetTitle() => "AssetRipper";
|
||||
public override string? GetTitle() => GameFileLoader.Premium ? Localization.AssetRipperPremium : Localization.AssetRipperFree;
|
||||
|
||||
public override void WriteInnerContent(TextWriter writer)
|
||||
{
|
||||
|
||||
@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace AssetRipper.GUI.Web;
|
||||
@ -98,11 +99,32 @@ public static class WebApplicationLauncher
|
||||
app.MapPost("/Resources/View", Pages.Resources.ViewPage.HandlePostRequest);
|
||||
app.MapPost("/Scenes/View", Pages.Scenes.ViewPage.HandlePostRequest);
|
||||
|
||||
app.MapPost("/Localization", (context) =>
|
||||
{
|
||||
context.Response.DisableCaching();
|
||||
if (context.Request.Query.TryGetValue("code", out StringValues code))
|
||||
{
|
||||
string? language = code;
|
||||
if (language is not null && LocalizationLoader.LanguageNameDictionary.ContainsKey(language))
|
||||
{
|
||||
Localization.LoadLanguage(language);
|
||||
}
|
||||
}
|
||||
return Results.Redirect("/").ExecuteAsync(context);
|
||||
});
|
||||
|
||||
//Commands
|
||||
app.MapPost("/Export", Commands.HandleCommand<Commands.Export>);
|
||||
app.MapPost("/Load", Commands.HandleCommand<Commands.Load>);
|
||||
app.MapPost("/LoadFile", Commands.HandleCommand<Commands.LoadFile>);
|
||||
app.MapPost("/LoadFolder", Commands.HandleCommand<Commands.LoadFolder>);
|
||||
app.MapPost("/Reset", Commands.HandleCommand<Commands.Reset>);
|
||||
|
||||
//Dialogs
|
||||
app.MapGet("/Dialogs/SaveFile", Dialogs.SaveFile.HandleGetRequest);
|
||||
app.MapGet("/Dialogs/OpenFolder", Dialogs.OpenFolder.HandleGetRequest);
|
||||
app.MapGet("/Dialogs/OpenFile", Dialogs.OpenFile.HandleGetRequest);
|
||||
app.MapGet("/Dialogs/OpenFiles", Dialogs.OpenFiles.HandleGetRequest);
|
||||
|
||||
app.Run();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user