ソースを参照

Merge pull request #1210 from PixiEditor/aseprite-space

Shortcuts import error handling
Krzysztof Krysiński 1 週間 前
コミット
f2454272fb

+ 3 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1142,5 +1142,7 @@
   "MITER_STROKE_JOIN": "Miter",
   "BEVEL_STROKE_JOIN": "Bevel",
   "TOGGLE_FULLSCREEN": "Toggle Fullscreen",
-  "TOGGLE_FULLSCREEN_DESCRIPTIVE": "Enter or Exit Fullscreen Mode"
+  "TOGGLE_FULLSCREEN_DESCRIPTIVE": "Enter or Exit Fullscreen Mode",
+  "SHORTCUTS_IMPORTED_WITH_ERRORS": "Importing shortcuts finished with errors.\nSome shortcuts were not recognized and have been skipped.",
+  "OPEN_ERROR_LOG": "Open error log"
 }

+ 11 - 1
src/PixiEditor/Models/Commands/ShortcutsTemplate.cs

@@ -11,6 +11,9 @@ public sealed class ShortcutsTemplate
 {
     public List<Shortcut> Shortcuts { get; set; }
 
+    [NonSerialized]
+    public List<string> Errors = new List<string>();
+
     public ShortcutsTemplate()
     {
         Shortcuts = new List<Shortcut>();
@@ -23,7 +26,14 @@ public sealed class ShortcutsTemplate
         {
             foreach (string command in keyDefinition.Commands)
             {
-                template.Shortcuts.Add(new Shortcut(keyDefinition.DefaultShortcut.ToKeyCombination(), command));
+                try
+                {
+                    template.Shortcuts.Add(new Shortcut(keyDefinition.DefaultShortcut.ToKeyCombination(), command));
+                }
+                catch (ArgumentException) // Invalid key
+                {
+                    template.Errors.Add($"Error for command {command}: Invalid key '{keyDefinition.DefaultShortcut.key}' with modifiers '{string.Join(", ", keyDefinition.DefaultShortcut.modifiers ?? [])}'");
+                }
             }
         }
 

+ 3 - 0
src/PixiEditor/Models/Commands/Templates/Providers/Parsers/KeyParser.cs

@@ -101,6 +101,9 @@ public static class KeyParser
             case ")":
                 parsed = Key.D0;
                 return true;
+            case "|":
+                parsed = Key.Oem5;
+                return true;
             default:
                 parsed = Key.None;
                 return false;

+ 43 - 75
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -41,12 +41,10 @@ internal class SettingsPage : ObservableObject
 internal partial class SettingsWindowViewModel : ViewModelBase
 {
     private string searchTerm;
-    
-    [ObservableProperty]
-    private int visibleGroups;
-    
-    [ObservableProperty]
-    private int currentPage;
+
+    [ObservableProperty] private int visibleGroups;
+
+    [ObservableProperty] private int currentPage;
 
     public bool ShowUpdateTab
     {
@@ -85,7 +83,8 @@ internal partial class SettingsWindowViewModel : ViewModelBase
     [Command.Internal("PixiEditor.Shortcuts.Reset")]
     public static async Task ResetCommand()
     {
-        await new OptionsDialog<string>("ARE_YOU_SURE", new LocalizedString("WARNING_RESET_SHORTCUTS_DEFAULT"), MainWindow.Current!)
+        await new OptionsDialog<string>("ARE_YOU_SURE", new LocalizedString("WARNING_RESET_SHORTCUTS_DEFAULT"),
+            MainWindow.Current!)
         {
             { new LocalizedString("YES"), x => CommandController.Current.ResetShortcuts() },
             new LocalizedString("CANCEL"),
@@ -112,31 +111,13 @@ internal partial class SettingsWindowViewModel : ViewModelBase
             SuggestedStartLocation = suggestedStartLocation,
             FileTypeChoices = new List<FilePickerFileType>()
             {
-                new FilePickerFileType("PixiShorts (*.pixisc)")
-                {
-                    Patterns = new List<string>
-                    {
-                        "*.pixisc"
-                    },
-                },
-                new FilePickerFileType("json (*.json)")
-                {
-                    Patterns = new List<string>
-                    {
-                        "*.json"
-                    },
-                },
-                new FilePickerFileType("All files (*.*)")
-                {
-                    Patterns = new List<string>
-                    {
-                        "*.*"
-                    },
-                },
+                new FilePickerFileType("PixiShorts (*.pixisc)") { Patterns = new List<string> { "*.pixisc" }, },
+                new FilePickerFileType("json (*.json)") { Patterns = new List<string> { "*.json" }, },
+                new FilePickerFileType("All files (*.*)") { Patterns = new List<string> { "*.*" }, },
             },
         });
-        
-        
+
+
         if (file is not null)
         {
             try
@@ -146,10 +127,11 @@ internal partial class SettingsWindowViewModel : ViewModelBase
             catch (Exception ex)
             {
                 string errMessageTrimmed = ex.Message.Length > 100 ? ex.Message[..100] + "..." : ex.Message;
-                NoticeDialog.Show(title: "ERROR", message: new LocalizedString("UNKNOWN_ERROR_SAVING").Value + $" {errMessageTrimmed}");
+                NoticeDialog.Show(title: "ERROR",
+                    message: new LocalizedString("UNKNOWN_ERROR_SAVING").Value + $" {errMessageTrimmed}");
             }
         }
-        
+
         // Sometimes, focus was brought back to the last edited shortcut
         // TODO: Keyboard.ClearFocus(); should be there but I can't find an equivalent from avalonia
     }
@@ -159,37 +141,20 @@ internal partial class SettingsWindowViewModel : ViewModelBase
     {
         List<FilePickerFileType> fileTypes = new List<FilePickerFileType>
         {
-            new("PixiShorts (*.pixisc)")
-            {
-                Patterns = new List<string>
-                {
-                    "*.pixisc"
-                },
-            },
-            new("json (*.json)")
-            {
-                Patterns = new List<string>
-                {
-                    "*.json"
-                },
-            },
+            new("PixiShorts (*.pixisc)") { Patterns = new List<string> { "*.pixisc" }, },
+            new("json (*.json)") { Patterns = new List<string> { "*.json" }, },
         };
-        
+
         customShortcutFormats ??= ShortcutProvider.GetProviders().OfType<ICustomShortcutFormat>().ToList();
         AddCustomParsersFormat(customShortcutFormats, fileTypes);
-        
-        fileTypes.Add(new FilePickerFileType("All files (*.*)")
-        {
-            Patterns = new List<string>
+
+        fileTypes.Add(new FilePickerFileType("All files (*.*)") { Patterns = new List<string> { "*.*" }, });
+
+        fileTypes.Insert(0,
+            new FilePickerFileType($"All Shortcut files {string.Join(",", fileTypes.SelectMany(a => a.Patterns))}")
             {
-                "*.*"
-            },
-        });
-        
-        fileTypes.Insert(0, new FilePickerFileType($"All Shortcut files {string.Join(",", fileTypes.SelectMany(a => a.Patterns))}")
-        {
-            Patterns = fileTypes.SelectMany(a => a.Patterns).ToList(),
-        });
+                Patterns = fileTypes.SelectMany(a => a.Patterns).ToList(),
+            });
 
         IStorageFolder? suggestedLocation = null;
         try
@@ -205,34 +170,35 @@ internal partial class SettingsWindowViewModel : ViewModelBase
 
         IReadOnlyList<IStorageFile> files = await MainWindow.Current!.StorageProvider.OpenFilePickerAsync(new()
         {
-            AllowMultiple = false,
-            SuggestedStartLocation = suggestedLocation,
-            FileTypeFilter = fileTypes,
+            AllowMultiple = false, SuggestedStartLocation = suggestedLocation, FileTypeFilter = fileTypes,
         });
-        
+
         if (files.Count > 0)
         {
             List<Shortcut> shortcuts = new List<Shortcut>();
-            if (!TryImport(files[0], ref shortcuts))
+            if (!TryImport(files[0], ref shortcuts, out var errors))
                 return;
-            
+
             CommandController.Current.ResetShortcuts();
             CommandController.Current.Import(shortcuts, false);
             File.Copy(files[0].Path.LocalPath, CommandController.ShortcutsPath, true);
-            NoticeDialog.Show("SHORTCUTS_IMPORTED_SUCCESS", "SUCCESS");
+            ImportShortcutTemplatePopup.DisplayImportSuccessInfo(null, errors);
         }
-        
+
         // Sometimes, focus was brought back to the last edited shortcut
         // TODO: Keyboard.ClearFocus(); should be there but I can't find an equivalent from avalonia
     }
 
-    private static bool TryImport(IStorageFile file, ref List<Shortcut> shortcuts)
+    private static bool TryImport(IStorageFile file, ref List<Shortcut> shortcuts, out List<string>? errors)
     {
+        errors = null;
         if (file.Name.EndsWith(".pixisc") || file.Name.EndsWith(".json"))
         {
             try
             {
-                shortcuts = ShortcutFile.LoadTemplate(file.Path.LocalPath)?.Shortcuts.ToList();
+                var template = ShortcutFile.LoadTemplate(file.Path.LocalPath);
+                errors = template.Errors;
+                shortcuts = template.Shortcuts.ToList();
             }
             catch (Exception)
             {
@@ -258,7 +224,9 @@ internal partial class SettingsWindowViewModel : ViewModelBase
 
             try
             {
-                shortcuts = provider.KeysParser.Parse(file.Path.LocalPath, false)?.Shortcuts.ToList();
+                var template = provider.KeysParser.Parse(file.Path.LocalPath, false);
+                shortcuts = template?.Shortcuts.ToList();
+                errors = template?.Errors;
             }
             catch (RecoverableException e)
             {
@@ -270,7 +238,8 @@ internal partial class SettingsWindowViewModel : ViewModelBase
         return true;
     }
 
-    private static void AddCustomParsersFormat(IList<ICustomShortcutFormat>? customFormats, List<FilePickerFileType> listToAddTo)
+    private static void AddCustomParsersFormat(IList<ICustomShortcutFormat>? customFormats,
+        List<FilePickerFileType> listToAddTo)
     {
         if (customFormats is null || customFormats.Count == 0)
             return;
@@ -355,6 +324,7 @@ internal partial class SettingsWindowViewModel : ViewModelBase
 
                 group.IsVisible = visibleCommands > 0;
             }
+
             return;
         }
 
@@ -381,8 +351,7 @@ internal partial class SettingsWindowViewModel : ViewModelBase
 
 internal partial class GroupSearchResult : ObservableObject
 {
-    [ObservableProperty]
-    private bool isVisible;
+    [ObservableProperty] private bool isVisible;
 
     public LocalizedString DisplayName { get; set; }
 
@@ -397,8 +366,7 @@ internal partial class GroupSearchResult : ObservableObject
 
 internal partial class CommandSearchResult : ObservableObject
 {
-    [ObservableProperty]
-    private bool isVisible;
+    [ObservableProperty] private bool isVisible;
 
     public Models.Commands.Commands.Command Command { get; set; }
 

+ 44 - 5
src/PixiEditor/Views/Shortcuts/ImportShortcutTemplatePopup.axaml.cs

@@ -6,6 +6,8 @@ using PixiEditor.Models.Commands;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Templates;
 using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.IO;
+using PixiEditor.OperatingSystem;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.Views.Dialogs;
 
@@ -38,7 +40,7 @@ internal partial class ImportShortcutTemplatePopup : PixiEditorPopup
         CommandController.Current.Import(defaults.DefaultShortcuts);
 
         if (!quiet)
-            Success(provider);
+            DisplayImportSuccessInfo(provider.Name, null);
     }
 
     [Command.Internal("PixiEditor.Shortcuts.Provider.ImportInstallation")]
@@ -55,10 +57,17 @@ internal partial class ImportShortcutTemplatePopup : PixiEditorPopup
         }
 
         CommandController.Current.ResetShortcuts();
+        List<string> errors = new List<string>();
 
         try
         {
-            CommandController.Current.Import(defaults.GetInstalledShortcuts().Shortcuts);
+            var template = defaults.GetInstalledShortcuts();
+            if (template?.Errors?.Count > 0)
+            {
+                errors.AddRange(template.Errors);
+            }
+
+            CommandController.Current.Import(template.Shortcuts);
         }
         catch (RecoverableException e)
         {
@@ -68,12 +77,42 @@ internal partial class ImportShortcutTemplatePopup : PixiEditorPopup
 
         if (!quiet)
         {
-            Success(provider);
+            DisplayImportSuccessInfo(provider.Name, errors);
         }
     }
 
-    private static void Success(ShortcutProvider provider) =>
-        NoticeDialog.Show(new LocalizedString("SHORTCUTS_IMPORTED", provider.Name), "SUCCESS");
+    public static void DisplayImportSuccessInfo(string? providerName, List<string>? errors)
+    {
+        string title = providerName != null ? "SHORTCUTS_IMPORTED" : "SHORTCUTS_IMPORTED_SUCCESS";
+        if (errors == null || errors.Count == 0)
+        {
+            NoticeDialog.Show(new LocalizedString(title, providerName), "SUCCESS");
+        }
+        else
+        {
+            string errorMessage = string.Join("\n", errors);
+            string errorLogPath = Path.Combine(Paths.TempFilesPath, "shortcut_import_errors.txt");
+            try
+            {
+                File.WriteAllText(errorLogPath, errorMessage);
+            }
+            catch
+            {
+                // ignored
+            }
+
+            OptionsDialog<string> dialog = new(
+                "WARNING",
+                new LocalizedString("SHORTCUTS_IMPORTED_WITH_ERRORS", providerName),
+                MainWindow.Current!)
+            {
+                { new LocalizedString("OPEN_ERROR_LOG"), x => { IOperatingSystem.Current.OpenUri(errorLogPath); } },
+                { new LocalizedString("CLOSE"), x => { } }
+            };
+
+            dialog.ShowDialog();
+        }
+    }
 
     // TODO figure out what these are for
     /*