Skip to content

Commit 74fce06

Browse files
Version 1.3
Added the ability to import and export changes.
1 parent b8eb7fc commit 74fce06

11 files changed

Lines changed: 379 additions & 65 deletions

src/App.axaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Avalonia.Controls.ApplicationLifetimes;
33
using Avalonia.Markup.Xaml;
44
namespace ARM9Editor;
5+
56
public partial class App : Application
67
{
78
public override void Initialize()

src/ConfigurationService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Diagnostics.CodeAnalysis;
33
using System.Reflection;
44
namespace ARM9Editor;
5+
56
public sealed class ConfigurationService
67
{
78
private static readonly Lazy<ConfigurationService> _instance = new(valueFactory: static () => new ConfigurationService());

src/DialogService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Avalonia.Controls;
22
namespace ARM9Editor;
3+
34
public static class DialogService
45
{
56
public static async Task ShowErrorAsync(Window? owner, string message)

src/EditorContent.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Avalonia.Layout;
44
using Avalonia.Media;
55
namespace ARM9Editor;
6+
67
public sealed class EditorContent : UserControl
78
{
89
private readonly EditorTab _tabType;

src/FileService.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
using Avalonia.Controls;
22
using Avalonia.Platform.Storage;
3+
using Newtonsoft.Json;
4+
using System.Diagnostics.CodeAnalysis;
35
namespace ARM9Editor;
6+
47
public sealed class FileService
58
{
69
private const int MinFileSize = 1024 * 1024;
710
private static readonly FilePickerFileType BinaryFileType = new("Binary files")
811
{
912
Patterns = new[] { "*.bin" }
1013
};
14+
private static readonly FilePickerFileType JsonFileType = new("JSON files")
15+
{
16+
Patterns = new[] { "*.json" }
17+
};
1118
public static async Task<(byte[]? Data, string? Path)> OpenFileAsync(Window? owner)
1219
{
1320
if (owner?.StorageProvider == null)
@@ -75,4 +82,66 @@ public static async Task SaveFileAsync(string path, byte[] data)
7582
});
7683
return file?.Path.LocalPath;
7784
}
85+
[RequiresUnreferencedCode("Calls Newtonsoft.Json.JsonConvert.SerializeObject")]
86+
public static async Task<string?> ExportChangesAsync(Window? owner, ChangesExport changes)
87+
{
88+
if (owner?.StorageProvider == null)
89+
{
90+
return null;
91+
}
92+
IStorageFile? file = await owner.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
93+
{
94+
Title = "Export Changes To JSON",
95+
DefaultExtension = "json",
96+
SuggestedFileName = "arm9_changes.json",
97+
FileTypeChoices = new[] { JsonFileType }
98+
});
99+
if (file == null)
100+
{
101+
return null;
102+
}
103+
try
104+
{
105+
string json = JsonConvert.SerializeObject(changes, Formatting.Indented);
106+
await File.WriteAllTextAsync(file.Path.LocalPath, json);
107+
return file.Path.LocalPath;
108+
}
109+
catch (Exception ex)
110+
{
111+
throw new IOException($"Failed to export changes: {ex.Message}", ex);
112+
}
113+
}
114+
[RequiresUnreferencedCode("Calls Newtonsoft.Json.JsonConvert.DeserializeObject")]
115+
public static async Task<ChangesExport?> ImportChangesAsync(Window? owner)
116+
{
117+
if (owner?.StorageProvider == null)
118+
{
119+
return null;
120+
}
121+
IReadOnlyList<IStorageFile> files = await owner.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
122+
{
123+
Title = "Import Changes From JSON",
124+
AllowMultiple = false,
125+
FileTypeFilter = new[] { JsonFileType }
126+
});
127+
if (files.Count == 0)
128+
{
129+
return null;
130+
}
131+
IStorageFile file = files[0];
132+
try
133+
{
134+
string json = await File.ReadAllTextAsync(file.Path.LocalPath);
135+
ChangesExport? changes = JsonConvert.DeserializeObject<ChangesExport>(json);
136+
return changes ?? throw new InvalidDataException("Invalid JSON format.");
137+
}
138+
catch (JsonException ex)
139+
{
140+
throw new InvalidDataException($"Failed to parse JSON: {ex.Message}", ex);
141+
}
142+
catch (Exception ex)
143+
{
144+
throw new IOException($"Failed to import changes: {ex.Message}", ex);
145+
}
146+
}
78147
}

src/MainWindow.axaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
<MenuItem Header="Open" Click="OnOpenClick" />
1515
<MenuItem Header="Save" Click="OnSaveClick" IsEnabled="{Binding IsFileLoaded}" />
1616
<MenuItem Header="Save As..." Click="OnSaveAsClick" IsEnabled="{Binding IsFileLoaded}" />
17+
<Separator />
18+
<MenuItem Header="Export Changes To..." Click="OnExportChangesClick" IsEnabled="{Binding IsFileLoaded}" />
19+
<MenuItem Header="Import Changes From..." Click="OnImportChangesClick" IsEnabled="{Binding IsFileLoaded}" />
1720
</MenuItem>
1821
<MenuItem Header="Help">
1922
<MenuItem Header="Info" Click="OnInfoClick" />

src/MainWindow.axaml.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Avalonia.Controls;
22
using Avalonia.Interactivity;
3+
using System.Diagnostics.CodeAnalysis;
34
namespace ARM9Editor;
5+
46
public partial class MainWindow : Window
57
{
68
private MainWindowViewModel? ViewModel => DataContext as MainWindowViewModel;
@@ -14,6 +16,7 @@ private void OnDataContextChanged(object? sender, EventArgs e)
1416
if (ViewModel != null)
1517
{
1618
ViewModel.Owner = this;
19+
ViewModel.OnTabsNeedRefresh = RefreshTabs;
1720
ViewModel.PropertyChanged += (_, args) =>
1821
{
1922
if (args.PropertyName == nameof(ViewModel.IsFileLoaded))
@@ -23,6 +26,10 @@ private void OnDataContextChanged(object? sender, EventArgs e)
2326
};
2427
}
2528
}
29+
private void RefreshTabs()
30+
{
31+
PopulateTabs();
32+
}
2633
private void PopulateTabs()
2734
{
2835
TabControl? tabControl = this.FindControl<TabControl>("MainTabControl");
@@ -63,6 +70,22 @@ private async void OnSaveAsClick(object? sender, RoutedEventArgs e)
6370
await ViewModel.SaveFileAsAsync();
6471
}
6572
}
73+
[RequiresUnreferencedCode("Calls ARM9Editor.MainWindowViewModel.ExportChangesAsync")]
74+
private async void OnExportChangesClick(object? sender, RoutedEventArgs e)
75+
{
76+
if (ViewModel != null)
77+
{
78+
await ViewModel.ExportChangesAsync();
79+
}
80+
}
81+
[RequiresUnreferencedCode("Calls ARM9Editor.MainWindowViewModel.ImportChangesAsync")]
82+
private async void OnImportChangesClick(object? sender, RoutedEventArgs e)
83+
{
84+
if (ViewModel != null)
85+
{
86+
await ViewModel.ImportChangesAsync();
87+
}
88+
}
6689
private async void OnInfoClick(object? sender, RoutedEventArgs e)
6790
{
6891
if (ViewModel != null)

src/MainWindowViewModel.cs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
using Avalonia.Controls;
22
using System.ComponentModel;
33
using System.Diagnostics;
4+
using System.Diagnostics.CodeAnalysis;
45
using System.Reflection;
56
using System.Runtime.CompilerServices;
67
namespace ARM9Editor;
8+
79
public sealed class MainWindowViewModel : INotifyPropertyChanged
810
{
911
private const string DefaultTitle = "Mario Kart DS ARM9 Editor";
1012
private const string RepoUrl = "https://github.com/LandonAndEmma/MKDS-ARM9-Editor";
1113
private string? _filePath;
1214
public event PropertyChangedEventHandler? PropertyChanged;
1315
public Window? Owner { get; set; }
16+
public Action? OnTabsNeedRefresh { get; set; }
1417
public ARM9Data Data { get; } = new();
1518
public bool IsFileLoaded => Data.IsLoaded;
1619
public string Status { get; private set => SetField(ref field, value); } = "Ready";
@@ -85,13 +88,66 @@ public async Task SaveFileAsAsync()
8588
await DialogService.ShowErrorAsync(Owner, ex.Message);
8689
}
8790
}
91+
[RequiresUnreferencedCode("Calls ARM9Editor.FileService.ExportChangesAsync")]
92+
public async Task ExportChangesAsync()
93+
{
94+
if (!IsFileLoaded)
95+
{
96+
await DialogService.ShowErrorAsync(Owner, "No file loaded.");
97+
return;
98+
}
99+
if (!Data.HasChanges())
100+
{
101+
await DialogService.ShowMessageAsync(Owner, "No Changes", "No changes have been made to export.");
102+
return;
103+
}
104+
try
105+
{
106+
ChangesExport changes = Data.ExportChanges();
107+
string? path = await FileService.ExportChangesAsync(Owner, changes);
108+
if (!string.IsNullOrEmpty(path))
109+
{
110+
Status = $"Exported {changes.Changes.Count} change(s) to: {Path.GetFileName(path)}";
111+
await DialogService.ShowMessageAsync(Owner, "Success", $"Successfully exported {changes.Changes.Count} change(s) to JSON.");
112+
}
113+
}
114+
catch (Exception ex)
115+
{
116+
await DialogService.ShowErrorAsync(Owner, ex.Message);
117+
}
118+
}
119+
[RequiresUnreferencedCode("Calls ARM9Editor.FileService.ImportChangesAsync")]
120+
public async Task ImportChangesAsync()
121+
{
122+
if (!IsFileLoaded)
123+
{
124+
await DialogService.ShowErrorAsync(Owner, "No file loaded.");
125+
return;
126+
}
127+
try
128+
{
129+
ChangesExport? changes = await FileService.ImportChangesAsync(Owner);
130+
if (changes == null)
131+
{
132+
return;
133+
}
134+
Data.ImportChanges(changes);
135+
OnTabsNeedRefresh?.Invoke();
136+
Status = $"Imported {changes.Changes.Count} change(s)";
137+
await DialogService.ShowMessageAsync(Owner, "Success", $"Successfully imported {changes.Changes.Count} change(s).");
138+
}
139+
catch (Exception ex)
140+
{
141+
await DialogService.ShowErrorAsync(Owner, ex.Message);
142+
}
143+
}
88144
public async Task ShowInfoAsync()
89145
{
90146
Assembly assembly = Assembly.GetExecutingAssembly();
91147
Version? version = assembly.GetName().Version;
92-
var versionStr = version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : "Unknown";
148+
string versionStr = version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : "Unknown";
93149
AssemblyCompanyAttribute? companyAttr = assembly.GetCustomAttribute<AssemblyCompanyAttribute>();
94-
var message = $"Mario Kart DS ARM9 Editor\nVersion: {versionStr}\n\nEdit values in Mario Kart DS ARM9 files.\n\n";
150+
string message = $"Mario Kart DS ARM9 Editor\nVersion: {versionStr}\n\nEdit values in Mario Kart DS ARM9 files.\n\n";
95151
if (companyAttr != null)
96152
{
97153
message += $"By: {companyAttr.Company}\n";

0 commit comments

Comments
 (0)