Add “Create Subfolder” export option (#1747)

* Add option to create subfolder during export operations

* Refactor export methods to remove createSubfolder parameter and adjust path handling
This commit is contained in:
John Soellner 2025-04-19 23:48:50 +02:00 committed by GitHub
parent 1a190ccb0f
commit 0dc5509a9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 63 additions and 6 deletions

View File

@ -90,6 +90,7 @@
"export_preparing": "Preparing for Export...\nThis might take a minute.",
"export_primary_content": "Export Primary Content",
"export_unity_project": "Export Unity Project",
"create_subfolder": "Create Subfolder",
"failed_files": "Failed Files",
"format": "Format",
"frequency": "Frequency",

View File

@ -53,7 +53,11 @@ public static class GameFileLoader
{
if (IsLoaded && IsValidExportDirectory(path))
{
Directory.Delete(path, true);
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
Directory.CreateDirectory(path);
ExportHandler.Export(GameData, path);
}
@ -63,13 +67,17 @@ public static class GameFileLoader
{
if (IsLoaded && IsValidExportDirectory(path))
{
Directory.Delete(path, true);
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
Directory.CreateDirectory(path);
Logger.Info(LogCategory.Export, "Starting export");
Logger.Info(LogCategory.Export, "Starting primary content export");
Logger.Info(LogCategory.Export, $"Attempting to export assets to {path}...");
Settings.ExportRootPath = path;
PrimaryContentExporter.CreateDefault(GameData).Export(GameBundle, Settings, LocalFileSystem.Instance);
Logger.Info(LogCategory.Export, "Finished exporting assets");
Logger.Info(LogCategory.Export, "Finished exporting primary content.");
}
}

View File

@ -20,6 +20,16 @@ public static class Commands
return builder.Accepts<PathFormData>("application/x-www-form-urlencoded");
}
private static bool TryGetCreateSubfolder(IFormCollection form)
{
if (form.TryGetValue("CreateSubfolder", out StringValues values))
{
return values == "true";
}
return false;
}
public readonly struct LoadFile : ICommand
{
static async Task<string?> ICommand.Execute(HttpRequest request)
@ -94,6 +104,8 @@ public static class Commands
if (!string.IsNullOrEmpty(path))
{
bool createSubfolder = TryGetCreateSubfolder(form);
path = MaybeAppendTimestampedSubfolder(path, createSubfolder);
GameFileLoader.ExportUnityProject(path);
}
return null;
@ -118,12 +130,26 @@ public static class Commands
if (!string.IsNullOrEmpty(path))
{
bool createSubfolder = TryGetCreateSubfolder(form);
path = MaybeAppendTimestampedSubfolder(path, createSubfolder);
GameFileLoader.ExportPrimaryContent(path);
}
return null;
}
}
private static string MaybeAppendTimestampedSubfolder(string path, bool append)
{
if (append)
{
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss");
string subfolder = $"AssetRipper_export_{timestamp}";
return Path.Combine(path, subfolder);
}
return path;
}
public readonly struct Reset : ICommand
{
static Task<string?> ICommand.Execute(HttpRequest request)

View File

@ -45,9 +45,25 @@ public sealed class CommandsPage : VuePage
.WithCustomAttribute("@input", "handleExportPathChange").Close();
}
using (new Div(writer).WithClass("form-check mb-2").End())
{
new Input(writer)
.WithType("checkbox")
.WithClass("form-check-input")
.WithCustomAttribute("v-model", "create_subfolder")
.WithCustomAttribute("@input", "handleExportPathChange")
.WithId("createSubfolder")
.Close();
new Label(writer)
.WithClass("form-check-label")
.WithCustomAttribute("for", "createSubfolder")
.Close(Localization.CreateSubfolder);
}
using (new Form(writer).WithAction("/Export/UnityProject").WithMethod("post").End())
{
new Input(writer).WithType("hidden").WithName("Path").WithCustomAttribute("v-model", "export_path").Close();
new Input(writer).WithType("hidden").WithName("CreateSubfolder").WithCustomAttribute("v-model", "create_subfolder").Close();
new Button(writer).WithCustomAttribute("v-if", "export_path === '' || export_path !== export_path.trim()").WithClass("btn btn-primary").WithCustomAttribute("disabled").Close(Localization.ExportUnityProject);
new Input(writer).WithCustomAttribute("v-else-if", "export_path_has_files").WithType("submit").WithClass("btn btn-danger").WithValue(Localization.ExportUnityProject).Close();
@ -57,6 +73,7 @@ public sealed class CommandsPage : VuePage
using (new Form(writer).WithAction("/Export/PrimaryContent").WithMethod("post").End())
{
new Input(writer).WithType("hidden").WithName("Path").WithCustomAttribute("v-model", "export_path").Close();
new Input(writer).WithType("hidden").WithName("CreateSubfolder").WithCustomAttribute("v-model", "create_subfolder").Close();
new Button(writer).WithCustomAttribute("v-if", "export_path === '' || export_path !== export_path.trim()").WithClass("btn btn-primary").WithCustomAttribute("disabled").Close(Localization.ExportPrimaryContent);
new Input(writer).WithCustomAttribute("v-else-if", "export_path_has_files").WithType("submit").WithClass("btn btn-danger").WithValue(Localization.ExportPrimaryContent).Close();

View File

@ -6,7 +6,8 @@ const app = createApp({
load_path: '',
load_path_exists: false,
export_path: '',
export_path_has_files: false
export_path_has_files: false,
create_subfolder: false
}
},
methods: {
@ -32,7 +33,11 @@ const app = createApp({
this.debouncedInput = setTimeout(async () => {
try {
this.export_path_has_files = await this.fetchDirectoryExists(this.export_path) && !(await this.fetchDirectoryEmpty(this.export_path));
if (this.create_subfolder) {
this.export_path_has_files = false;
} else {
this.export_path_has_files = await this.fetchDirectoryExists(this.export_path) && !(await this.fetchDirectoryEmpty(this.export_path));
}
} catch (error) {
console.error('Error fetching data:', error);
}