Skip to content

Commit 42985bb

Browse files
authored
feat: add Simulate Update UI (model, view, viewmodel, nav entry) (#32)
- Add SimulateConfigModel with all configuration fields - Add SimulateViewModel with folder/file pickers and placeholder logic - Add SimulateView.axaml with three sections (target, config, output) - Add Simulate nav item to MainWindowViewModel - Add i18n strings for zh-CN and en-US
1 parent c985535 commit 42985bb

6 files changed

Lines changed: 249 additions & 1 deletion

File tree

src/Models/SimulateConfigModel.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
3+
namespace GeneralUpdate.Tools.Models;
4+
5+
/// <summary>
6+
/// Configuration for the simulate-update module.
7+
/// </summary>
8+
public partial class SimulateConfigModel : ObservableObject
9+
{
10+
/// <summary>User-provided old-version app directory to test against.</summary>
11+
[ObservableProperty] private string _appDirectory = string.Empty;
12+
13+
/// <summary>Path to a patch .zip generated by the Patch module.</summary>
14+
[ObservableProperty] private string _patchFilePath = string.Empty;
15+
16+
/// <summary>The current version of the app being tested.</summary>
17+
[ObservableProperty] private string _currentVersion = "1.0.0.0";
18+
19+
/// <summary>The target version the patch upgrades to.</summary>
20+
[ObservableProperty] private string _targetVersion = "2.0.0.0";
21+
22+
/// <summary>Platform selector (1=Windows, 2=Linux).</summary>
23+
[ObservableProperty] private int _platform = 1;
24+
25+
/// <summary>AppType sent to the server (1=ClientApp, 2=UpgradeApp).</summary>
26+
[ObservableProperty] private int _appType = 1;
27+
28+
/// <summary>Application secret key for the update API.</summary>
29+
[ObservableProperty] private string _appSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6";
30+
31+
/// <summary>Product identifier.</summary>
32+
[ObservableProperty] private string _productId = "2d974e2a-31e6-4887-9bb1-b4689e98c77a";
33+
34+
/// <summary>Directory where client.cs / upgrade.cs and server are generated.</summary>
35+
[ObservableProperty] private string _outputDirectory = string.Empty;
36+
}

src/Services/LocalizationService.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ public string this[string key]
115115
["Theme.Light"] = "浅色",
116116
["Theme.Dark"] = "深色",
117117
["Theme.Toggle"] = "切换主题",
118+
["Nav.Simulate"] = "模拟更新",
119+
["Sim.Title"] = "模拟更新",
120+
["Sim.TestTarget"] = "测试目标",
121+
["Sim.OldAppDir"] = "旧版本应用目录",
122+
["Sim.PatchFile"] = "补丁包文件",
123+
["Sim.Select"] = "选择",
124+
["Sim.UpdateConfig"] = "更新配置",
125+
["Sim.CurrentVer"] = "当前版本",
126+
["Sim.TargetVer"] = "目标版本",
127+
["Sim.Platform"] = "平台",
128+
["Sim.AppType"] = "应用类型",
129+
["Sim.AppSecret"] = "应用密钥",
130+
["Sim.ProductId"] = "产品ID",
131+
["Sim.Output"] = "输出",
132+
["Sim.OutputDir"] = "模拟目录",
133+
["Sim.Start"] = "开始模拟",
134+
["Sim.SelectAppDir"] = "选择旧版本应用目录",
135+
["Sim.SelectPatch"] = "选择补丁包",
136+
["Sim.SelectOutput"] = "选择模拟输出目录",
137+
["Sim.ValidateDirs"] = "请填写所有必填项",
138+
["Sim.DotnetCheck"] = "需要 .NET 10.0 SDK,请先安装",
118139
},
119140
["en-US"] = new()
120141
{
@@ -187,6 +208,27 @@ public string this[string key]
187208
["Theme.Light"] = "Light",
188209
["Theme.Dark"] = "Dark",
189210
["Theme.Toggle"] = "Toggle Theme",
211+
["Nav.Simulate"] = "Simulate",
212+
["Sim.Title"] = "Simulate Update",
213+
["Sim.TestTarget"] = "Test Target",
214+
["Sim.OldAppDir"] = "Old App Directory",
215+
["Sim.PatchFile"] = "Patch Package",
216+
["Sim.Select"] = "Select",
217+
["Sim.UpdateConfig"] = "Update Config",
218+
["Sim.CurrentVer"] = "Current Version",
219+
["Sim.TargetVer"] = "Target Version",
220+
["Sim.Platform"] = "Platform",
221+
["Sim.AppType"] = "App Type",
222+
["Sim.AppSecret"] = "App Secret",
223+
["Sim.ProductId"] = "Product ID",
224+
["Sim.Output"] = "Output",
225+
["Sim.OutputDir"] = "Simulate Directory",
226+
["Sim.Start"] = "Start Simulation",
227+
["Sim.SelectAppDir"] = "Select old version directory",
228+
["Sim.SelectPatch"] = "Select patch package",
229+
["Sim.SelectOutput"] = "Select simulation output directory",
230+
["Sim.ValidateDirs"] = "Please fill in all required fields",
231+
["Sim.DotnetCheck"] = ".NET 10.0 SDK is required. Please install it first.",
190232
}
191233
};
192234

src/ViewModels/MainWindowViewModel.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ private void SyncNavItems()
3535
NavItems.Add(new("Patch", _loc["Nav.Patch"], typeof(PatchViewModel), true));
3636
NavItems.Add(new("Extension", _loc["Nav.Extension"], typeof(ExtensionViewModel), false));
3737
NavItems.Add(new("OSS", _loc["Nav.OSS"], typeof(OSSViewModel), false));
38+
NavItems.Add(new("Simulate", _loc["Nav.Simulate"], typeof(SimulateViewModel), false));
3839
}
3940

4041
[RelayCommand]
@@ -46,7 +47,8 @@ private void Navigate(NavItem item)
4647
{
4748
"Patch" => new PatchViewModel(),
4849
"Extension" => new ExtensionViewModel(),
49-
_ => new OSSViewModel()
50+
"OSS" => new OSSViewModel(),
51+
_ => new SimulateViewModel()
5052
};
5153
}
5254

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Collections.ObjectModel;
3+
using System.Threading.Tasks;
4+
using CommunityToolkit.Mvvm.ComponentModel;
5+
using CommunityToolkit.Mvvm.Input;
6+
using GeneralUpdate.Tools.Models;
7+
using GeneralUpdate.Tools.Services;
8+
9+
namespace GeneralUpdate.Tools.ViewModels;
10+
11+
public partial class SimulateViewModel : ViewModelBase
12+
{
13+
private readonly LocalizationService _loc = LocalizationService.Instance;
14+
15+
public SimulateConfigModel Config { get; } = new();
16+
17+
[ObservableProperty] private bool _isRunning;
18+
[ObservableProperty] private string _status;
19+
[ObservableProperty] private ObservableCollection<string> _log = new();
20+
21+
public ObservableCollection<PlatformItem> Platforms { get; } = new()
22+
{
23+
new(1, "Windows"),
24+
new(2, "Linux")
25+
};
26+
27+
public ObservableCollection<AppTypeItem> AppTypes { get; } = new()
28+
{
29+
new(1, "ClientApp"),
30+
new(2, "UpgradeApp")
31+
};
32+
33+
public SimulateViewModel()
34+
{
35+
_status = _loc["Patch.Ready"];
36+
}
37+
38+
async Task<string?> PickFolder(string title)
39+
{
40+
var tl = Avalonia.Controls.TopLevel.GetTopLevel(
41+
(Avalonia.Application.Current?.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime)?.MainWindow);
42+
if (tl == null) return null;
43+
var r = await tl.StorageProvider.OpenFolderPickerAsync(
44+
new Avalonia.Platform.Storage.FolderPickerOpenOptions { Title = title, AllowMultiple = false });
45+
return r.Count > 0 ? r[0].Path.LocalPath : null;
46+
}
47+
48+
async Task<string?> PickFile(string title)
49+
{
50+
var tl = Avalonia.Controls.TopLevel.GetTopLevel(
51+
(Avalonia.Application.Current?.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime)?.MainWindow);
52+
if (tl == null) return null;
53+
var r = await tl.StorageProvider.OpenFilePickerAsync(
54+
new Avalonia.Platform.Storage.FilePickerOpenOptions { Title = title, AllowMultiple = false });
55+
return r.Count > 0 ? r[0].Path.LocalPath : null;
56+
}
57+
58+
[RelayCommand] async Task SelectAppDir() { var p = await PickFolder("选择旧版本应用目录"); if (p != null) Config.AppDirectory = p; }
59+
[RelayCommand] async Task SelectPatch() { var p = await PickFile("选择补丁包"); if (p != null) Config.PatchFilePath = p; }
60+
[RelayCommand] async Task SelectOutputDir() { var p = await PickFolder("选择模拟输出目录"); if (p != null) Config.OutputDirectory = p; }
61+
62+
[RelayCommand]
63+
async Task StartSimulation()
64+
{
65+
// Validation will be implemented in issue #4
66+
if (string.IsNullOrWhiteSpace(Config.AppDirectory)) { Status = "请选择旧版本应用目录"; return; }
67+
if (string.IsNullOrWhiteSpace(Config.PatchFilePath)) { Status = "请选择补丁包文件"; return; }
68+
if (string.IsNullOrWhiteSpace(Config.OutputDirectory)) { Status = "请选择模拟输出目录"; return; }
69+
Status = "模拟功能将在后续 issue 中实现";
70+
}
71+
72+
void L(string msg) => Log.Add($"[{DateTime.Now:HH:mm:ss}] {msg}");
73+
}
74+
75+
public record PlatformItem(int Value, string DisplayName) { public override string ToString() => DisplayName; }
76+
public record AppTypeItem(int Value, string DisplayName) { public override string ToString() => DisplayName; }

src/Views/SimulateView.axaml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:vm="using:GeneralUpdate.Tools.ViewModels"
4+
xmlns:svc="using:GeneralUpdate.Tools.Services"
5+
x:Class="GeneralUpdate.Tools.Views.SimulateView"
6+
x:DataType="vm:SimulateViewModel">
7+
<ScrollViewer>
8+
<StackPanel Margin="28,24" Spacing="14">
9+
<TextBlock Text="🧪 Simulate Update" FontSize="20" FontWeight="Bold"/>
10+
11+
<!-- Test target -->
12+
<Border Padding="16" CornerRadius="8" Background="{DynamicResource SystemControlBackgroundChromeMediumBrush}">
13+
<StackPanel Spacing="10">
14+
<TextBlock Text="Test Target" FontSize="14" FontWeight="SemiBold"/>
15+
<Grid ColumnDefinitions="Auto,*,Auto">
16+
<TextBlock Grid.Column="0" Text="Old App Dir" VerticalAlignment="Center" Width="100"/>
17+
<TextBox Grid.Column="1" Text="{Binding Config.AppDirectory}" IsReadOnly="True" Margin="8,0"/>
18+
<Button Grid.Column="2" Content="📁 Select" Command="{Binding SelectAppDirCommand}" MinWidth="80"/>
19+
</Grid>
20+
<Grid ColumnDefinitions="Auto,*,Auto">
21+
<TextBlock Grid.Column="0" Text="Patch Package" VerticalAlignment="Center" Width="100"/>
22+
<TextBox Grid.Column="1" Text="{Binding Config.PatchFilePath}" IsReadOnly="True" Margin="8,0"/>
23+
<Button Grid.Column="2" Content="📁 Select" Command="{Binding SelectPatchCommand}" MinWidth="80"/>
24+
</Grid>
25+
</StackPanel>
26+
</Border>
27+
28+
<!-- Config -->
29+
<Border Padding="16" CornerRadius="8" Background="{DynamicResource SystemControlBackgroundChromeMediumBrush}">
30+
<StackPanel Spacing="10">
31+
<TextBlock Text="Update Config" FontSize="14" FontWeight="SemiBold"/>
32+
<Grid ColumnDefinitions="Auto,*,Auto,*">
33+
<TextBlock Grid.Column="0" Text="Current Ver" VerticalAlignment="Center"/>
34+
<TextBox Grid.Column="1" Text="{Binding Config.CurrentVersion}" Margin="8,0,16,0"/>
35+
<TextBlock Grid.Column="2" Text="Target Ver" VerticalAlignment="Center"/>
36+
<TextBox Grid.Column="3" Text="{Binding Config.TargetVersion}" Margin="8,0"/>
37+
</Grid>
38+
<Grid ColumnDefinitions="Auto,*,Auto,*">
39+
<TextBlock Grid.Column="0" Text="Platform" VerticalAlignment="Center"/>
40+
<ComboBox Grid.Column="1" ItemsSource="{Binding Platforms}"
41+
SelectedItem="{Binding Config.Platform}" Margin="8,0,16,0"/>
42+
<TextBlock Grid.Column="2" Text="AppType" VerticalAlignment="Center"/>
43+
<ComboBox Grid.Column="3" ItemsSource="{Binding AppTypes}"
44+
SelectedItem="{Binding Config.AppType}" Margin="8,0"/>
45+
</Grid>
46+
<Grid ColumnDefinitions="Auto,*">
47+
<TextBlock Grid.Column="0" Text="AppSecret" VerticalAlignment="Center"/>
48+
<TextBox Grid.Column="1" Text="{Binding Config.AppSecretKey}" Margin="8,0"/>
49+
</Grid>
50+
<Grid ColumnDefinitions="Auto,*">
51+
<TextBlock Grid.Column="0" Text="Product ID" VerticalAlignment="Center"/>
52+
<TextBox Grid.Column="1" Text="{Binding Config.ProductId}" Margin="8,0"/>
53+
</Grid>
54+
</StackPanel>
55+
</Border>
56+
57+
<!-- Output -->
58+
<Border Padding="16" CornerRadius="8" Background="{DynamicResource SystemControlBackgroundChromeMediumBrush}">
59+
<StackPanel Spacing="10">
60+
<TextBlock Text="Output" FontSize="14" FontWeight="SemiBold"/>
61+
<Grid ColumnDefinitions="Auto,*,Auto">
62+
<TextBlock Grid.Column="0" Text="Simulate Dir" VerticalAlignment="Center" Width="100"/>
63+
<TextBox Grid.Column="1" Text="{Binding Config.OutputDirectory}" IsReadOnly="True" Margin="8,0"/>
64+
<Button Grid.Column="2" Content="📁 Select" Command="{Binding SelectOutputDirCommand}" MinWidth="80"/>
65+
</Grid>
66+
</StackPanel>
67+
</Border>
68+
69+
<!-- Run -->
70+
<Button Content="🚀 Start Simulation" Command="{Binding StartSimulationCommand}"
71+
IsEnabled="{Binding !IsRunning}" Height="40" FontSize="14" HorizontalAlignment="Stretch"/>
72+
<TextBlock Text="{Binding Status}" FontSize="13"/>
73+
74+
<!-- Log -->
75+
<Border Padding="10" CornerRadius="6" Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" MaxHeight="200">
76+
<ListBox ItemsSource="{Binding Log}" Background="Transparent" BorderThickness="0">
77+
<ListBox.ItemTemplate>
78+
<DataTemplate><TextBlock Text="{Binding}" FontSize="11" FontFamily="Consolas,monospace"/></DataTemplate>
79+
</ListBox.ItemTemplate>
80+
</ListBox>
81+
</Border>
82+
</StackPanel>
83+
</ScrollViewer>
84+
</UserControl>

src/Views/SimulateView.axaml.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Avalonia.Controls;
2+
3+
namespace GeneralUpdate.Tools.Views;
4+
5+
public partial class SimulateView : UserControl
6+
{
7+
public SimulateView() => InitializeComponent();
8+
}

0 commit comments

Comments
 (0)