From 36ad881a8dfdfba455e2ddcb5dbc68bf961513b5 Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Mon, 16 Mar 2026 21:47:08 +0200 Subject: [PATCH] Refactor right click commands --- FModel/Enums.cs | 1 + FModel/ViewModels/CUE4ParseViewModel.cs | 3 + .../Commands/RightClickMenuCommand.cs | 289 ++++++++---------- FModel/ViewModels/Commands/TabCommand.cs | 12 +- .../ContextMenus/FileContextMenu.xaml | 12 +- .../ContextMenus/FolderContextMenu.xaml | 12 +- FModel/Views/Resources/Resources.xaml | 12 +- FModel/Views/SearchView.xaml | 24 +- 8 files changed, 161 insertions(+), 204 deletions(-) diff --git a/FModel/Enums.cs b/FModel/Enums.cs index bf85c984..2bb4c25d 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -107,6 +107,7 @@ public enum EBulkType Animations = 1 << 4, Audio = 1 << 5, Code = 1 << 6, + Raw = 1 << 7, } public enum EAssetCategory : uint diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 6b13ecc4..f2805a80 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -598,6 +598,9 @@ public class CUE4ParseViewModel : ViewModel foreach (var f in folder.Folders) ExportFolder(cancellationToken, f); } + public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder, EBulkType bulk) + => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk)); + public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder) => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs)); diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index f4456ed9..500015ed 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -1,7 +1,10 @@ +using System; using System.Collections; +using System.IO; using System.Linq; using System.Threading; using CUE4Parse.FileProvider.Objects; +using CUE4Parse.Utils; using FModel.Framework; using FModel.Services; using FModel.Settings; @@ -17,6 +20,21 @@ public class RightClickMenuCommand : ViewModelCommand { } + private enum EAction + { + Show, + Export, + } + + private enum EShowAssetType + { + None, + JSON, + Metadata, + References, + Decompile, + } + public override async void Execute(ApplicationViewModel contextViewModel, object parameter) { if (parameter is not object[] parameters || parameters[0] is not string trigger) @@ -26,188 +44,123 @@ public class RightClickMenuCommand : ViewModelCommand if (param.Length == 0) return; var folders = param.OfType().ToArray(); - var assets = param.SelectMany(item => item switch - { - GameFile gf => new[] { gf }, // search view passes GameFile directly - GameFileViewModel gvm => new[] { gvm.Asset }, - _ => [] - }).ToArray(); + var assets = param + .Select(static item => item switch + { + GameFile gf => gf, + GameFileViewModel gvm => gvm.Asset, + _ => null + }) + .Where(static gf => gf is not null).ToArray(); if (folders.Length == 0 && assets.Length == 0) return; - var updateUi = assets.Length > 1 ? EBulkType.Auto : EBulkType.None; + var assetsGroups = assets.GroupBy(static gf => gf.Directory); + var (action, showtype, bulktype) = trigger switch + { + "Assets_Extract_New_Tab" => (EAction.Show, EShowAssetType.JSON, EBulkType.None), + "Assets_Show_Metadata" => (EAction.Show, EShowAssetType.Metadata, EBulkType.None), + "Assets_Show_References" => (EAction.Show, EShowAssetType.References, EBulkType.None), + "Assets_Decompile" => (EAction.Show, EShowAssetType.Decompile, EBulkType.Code), + + "Save_Data" => (EAction.Export, EShowAssetType.None, EBulkType.Raw), + "Save_Properties" => (EAction.Export, EShowAssetType.None, EBulkType.Properties), + "Save_Textures" => (EAction.Export, EShowAssetType.None, EBulkType.Textures), + "Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes), + "Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations), + "Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio), + + _ => throw new ArgumentOutOfRangeException("Unsupported asset action."), + }; + await _threadWorkerView.Begin(cancellationToken => { - switch (trigger) + if (action is EAction.Show) { - #region Asset Commands - case "Assets_Extract_New_Tab": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true); - } - break; - case "Assets_Show_Metadata": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.ShowMetadata(entry); - } - break; - case "Assets_Show_References": - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.FindReferences(assets.FirstOrDefault()); - } - break; - case "Assets_Decompile": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Decompile(entry); - } - break; - case "Assets_Export_Data": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.ExportData(entry); - } - break; - case "Assets_Save_Properties": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Properties | updateUi); - } - break; - case "Assets_Save_Textures": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Textures | updateUi); - } - break; - case "Assets_Save_Models": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Meshes | updateUi); - } - break; - case "Assets_Save_Animations": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi); - } - break; - case "Assets_Save_Audio": - foreach (var entry in assets) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Audio | updateUi); - } - break; - #endregion + if (showtype is EShowAssetType.References) + assets = [assets.FirstOrDefault()]; - #region Folder Commands - case "Folders_Export_Data": - foreach (var folder in folders) - { - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder); + Action entryAction = showtype switch + { + EShowAssetType.JSON => entry => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true), + EShowAssetType.Metadata => entry => contextViewModel.CUE4Parse.ShowMetadata(entry), + EShowAssetType.Decompile => entry => contextViewModel.CUE4Parse.Decompile(entry), + EShowAssetType.References => entry => contextViewModel.CUE4Parse.FindReferences(entry), + _ => throw new ArgumentOutOfRangeException("Unsupported asset action type."), + }; - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully exported ", Constants.WHITE); - FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true); - }); - } - break; - case "Folders_Save_Properties": - foreach (var folder in folders) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.SaveFolder(cancellationToken, folder); + foreach (var entry in assets) + { + Thread.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + entryAction(entry); + } - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully saved ", Constants.WHITE); - FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true); - }); - } - break; - case "Folders_Save_Textures": - foreach (var folder in folders) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.TextureFolder(cancellationToken, folder); + return; + } - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully saved textures from ", Constants.WHITE); - FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true); - }); - } - break; - case "Folders_Save_Models": - foreach (var folder in folders) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.ModelFolder(cancellationToken, folder); + var (dirType, filetype) = bulktype switch + { + EBulkType.Raw => (UserSettings.Default.RawDataDirectory, "files"), + EBulkType.Properties => (UserSettings.Default.PropertiesDirectory, "json files"), + EBulkType.Textures => (UserSettings.Default.TextureDirectory, "textures"), + EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"), + EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"), + EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"), + _ => (null, null), + }; - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully saved models from ", Constants.WHITE); - FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true); - }); - } - break; - case "Folders_Save_Animations": - foreach (var folder in folders) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.AnimationFolder(cancellationToken, folder); + if (string.IsNullOrEmpty(dirType)) + return; - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully saved animations from ", Constants.WHITE); - FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true); - }); - } - break; - case "Folders_Save_Audio": - foreach (var folder in folders) - { - Thread.Yield(); - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.AudioFolder(cancellationToken, folder); + Action folderAction = bulktype switch + { + EBulkType.Raw => folder => contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder), + _ => folder => contextViewModel.CUE4Parse.ExtractFolder(cancellationToken, folder, bulktype | EBulkType.Auto), + }; - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully saved audio from ", Constants.WHITE); - FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true); - }); - } - break; - #endregion + foreach (var folder in folders) + { + cancellationToken.ThrowIfCancellationRequested(); + folderAction(folder); + + var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? folder.PathAtThisPoint : folder.PathAtThisPoint.SubstringAfterLast('/')).Replace('\\', '/'); + FLogger.Append(ELog.Information, () => + { + FLogger.Text($"Successfully exported {filetype} from ", Constants.WHITE); + FLogger.Link(folder.PathAtThisPoint, path, true); + }); + } + + Action fileAction = bulktype switch + { + EBulkType.Raw => (entry, _, update) => contextViewModel.CUE4Parse.ExportData(entry, !update), + _ => (entry, bulk, update) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk), + }; + + foreach (var group in assetsGroups) + { + var directory = group.Key; + var list = group.ToArray(); + var update = list.Length > 1; + var bulk = bulktype | (update ? EBulkType.Auto : EBulkType.None); + foreach (var entry in list) + { + Thread.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + fileAction(entry, bulk, update); + } + + if (update) + { + var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? directory : directory.SubstringAfterLast('/')).Replace('\\', '/'); + FLogger.Append(ELog.Information, () => + { + FLogger.Text($"Successfully exported {list.Length} {filetype} from ", Constants.WHITE); + FLogger.Link(directory, path, true); + }); + } } }); } diff --git a/FModel/ViewModels/Commands/TabCommand.cs b/FModel/ViewModels/Commands/TabCommand.cs index 07d1f6c0..a622d5e4 100644 --- a/FModel/ViewModels/Commands/TabCommand.cs +++ b/FModel/ViewModels/Commands/TabCommand.cs @@ -34,34 +34,34 @@ public class TabCommand : ViewModelCommand case "Find_References": _applicationView.CUE4Parse.FindReferences(tabViewModel.Entry); break; - case "Asset_Export_Data": + case "Save_Data": await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry)); break; - case "Asset_Save_Properties": + case "Save_Properties": await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Properties); }); break; - case "Asset_Save_Textures": + case "Save_Textures": await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Textures); }); break; - case "Asset_Save_Models": + case "Save_Models": await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes); }); break; - case "Asset_Save_Animations": + case "Save_Animations": await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations); }); break; - case "Asset_Save_Audio": + case "Save_Audio": await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Audio); diff --git a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml index 109b71fb..28196a50 100644 --- a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml +++ b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml @@ -120,7 +120,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -150,7 +150,7 @@ - + @@ -176,7 +176,7 @@ - + @@ -202,7 +202,7 @@ - + @@ -228,7 +228,7 @@ - + diff --git a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml index 7ad5f5a8..ca5a5871 100644 --- a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml +++ b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml @@ -9,7 +9,7 @@ Command="{Binding RightClickMenuCommand}"> - + @@ -29,7 +29,7 @@ Command="{Binding RightClickMenuCommand}"> - + @@ -49,7 +49,7 @@ Command="{Binding RightClickMenuCommand}"> - + @@ -69,7 +69,7 @@ Command="{Binding RightClickMenuCommand}"> - + @@ -89,7 +89,7 @@ Command="{Binding RightClickMenuCommand}"> - + @@ -109,7 +109,7 @@ Command="{Binding RightClickMenuCommand}"> - + diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index c99c9de0..715a5b1f 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -926,7 +926,7 @@ - + @@ -938,7 +938,7 @@ - + @@ -947,7 +947,7 @@ - + @@ -956,7 +956,7 @@ - + @@ -965,7 +965,7 @@ - + @@ -974,7 +974,7 @@ - + diff --git a/FModel/Views/SearchView.xaml b/FModel/Views/SearchView.xaml index 1b37d87a..9e3a1f93 100644 --- a/FModel/Views/SearchView.xaml +++ b/FModel/Views/SearchView.xaml @@ -216,7 +216,7 @@ - + @@ -231,7 +231,7 @@ - + @@ -246,7 +246,7 @@ - + @@ -261,7 +261,7 @@ - + @@ -276,7 +276,7 @@ - + @@ -291,7 +291,7 @@ - + @@ -561,7 +561,7 @@ - + @@ -576,7 +576,7 @@ - + @@ -591,7 +591,7 @@ - + @@ -606,7 +606,7 @@ - + @@ -621,7 +621,7 @@ - + @@ -636,7 +636,7 @@ - +