From 618235d8ed626b80ca4ee4fbc41f1c8e3ad9424c Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Tue, 28 Apr 2026 09:25:34 +0200 Subject: [PATCH] feat(ui): add About modal opened from Help menu Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ViewModels/IslandsShellViewModel.cs | 11 +++++ .../ViewModels/Modals/AboutModalViewModel.cs | 34 +++++++++++++ src/ClaudeDo.Ui/Views/MainWindow.axaml | 1 + src/ClaudeDo.Ui/Views/MainWindow.axaml.cs | 8 +++ .../Views/Modals/AboutModalView.axaml | 49 +++++++++++++++++++ .../Views/Modals/AboutModalView.axaml.cs | 10 ++++ 6 files changed, 113 insertions(+) create mode 100644 src/ClaudeDo.Ui/ViewModels/Modals/AboutModalViewModel.cs create mode 100644 src/ClaudeDo.Ui/Views/Modals/AboutModalView.axaml create mode 100644 src/ClaudeDo.Ui/Views/Modals/AboutModalView.axaml.cs diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index 4a7f600..53146e6 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -8,6 +8,7 @@ using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Islands; +using ClaudeDo.Ui.ViewModels.Modals; using ClaudeDo.Ui.ViewModels.Planning; using Microsoft.EntityFrameworkCore; @@ -35,6 +36,9 @@ public sealed partial class IslandsShellViewModel : ViewModelBase // Set by MainWindow to open the conflict resolution dialog. public Func? ShowConflictDialog { get; set; } + // Set by MainWindow to open the About dialog. + public Func? ShowAboutModal { get; set; } + [ObservableProperty] private bool _isUpdateBannerVisible; [ObservableProperty] private string? _updateBannerLatestVersion; [ObservableProperty] private string? _inlineUpdateStatus; @@ -222,6 +226,13 @@ public sealed partial class IslandsShellViewModel : ViewModelBase if (InlineUpdateStatus == text) InlineUpdateStatus = null; } + [RelayCommand] + private async Task OpenAbout() + { + var vm = new AboutModalViewModel(); + if (ShowAboutModal is not null) await ShowAboutModal(vm); + } + [RelayCommand] private async Task CheckForUpdatesAsync() { diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/AboutModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/AboutModalViewModel.cs new file mode 100644 index 0000000..a3280be --- /dev/null +++ b/src/ClaudeDo.Ui/ViewModels/Modals/AboutModalViewModel.cs @@ -0,0 +1,34 @@ +using System.Diagnostics; +using System.IO; +using System.Reflection; +using ClaudeDo.Data; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace ClaudeDo.Ui.ViewModels.Modals; + +public sealed partial class AboutModalViewModel : ViewModelBase +{ + public string AppVersion { get; } = + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "0.0.0"; + public string DataFolderPath { get; } = Paths.AppDataRoot(); + public string LogsFolderPath { get; } = Path.Combine(Paths.AppDataRoot(), "logs"); + public string WorkerConfigPath { get; } = Path.Combine(Paths.AppDataRoot(), "worker.config.json"); + + public Action? CloseAction { get; set; } + + [RelayCommand] private void Close() => CloseAction?.Invoke(); + + [RelayCommand] + private void OpenPath(string? path) + { + if (string.IsNullOrWhiteSpace(path)) return; + try + { + var target = File.Exists(path) ? path : (Directory.Exists(path) ? path : null); + if (target is null) return; + Process.Start(new ProcessStartInfo("explorer.exe", $"\"{target}\"") { UseShellExecute = true }); + } + catch { /* ignore */ } + } +} diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml b/src/ClaudeDo.Ui/Views/MainWindow.axaml index 1d6d5d4..58bde2d 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml @@ -67,6 +67,7 @@ Foreground="{DynamicResource TextDimBrush}"> + diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs index 463700f..4ad9502 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs @@ -1,6 +1,7 @@ using Avalonia.Controls; using Avalonia.Input; using ClaudeDo.Ui.ViewModels; +using ClaudeDo.Ui.Views.Modals; using ClaudeDo.Ui.Views.Planning; namespace ClaudeDo.Ui.Views; @@ -23,6 +24,13 @@ public partial class MainWindow : Window var modal = new ConflictResolutionView { DataContext = conflictVm }; await modal.ShowDialog(this); }; + vm.ShowAboutModal = async (aboutVm) => + { + var dlg = new AboutModalView { DataContext = aboutVm }; + var tcs = new TaskCompletionSource(); + aboutVm.CloseAction = () => { dlg.Close(); tcs.TrySetResult(true); }; + await dlg.ShowDialog(this); + }; } } diff --git a/src/ClaudeDo.Ui/Views/Modals/AboutModalView.axaml b/src/ClaudeDo.Ui/Views/Modals/AboutModalView.axaml new file mode 100644 index 0000000..b595647 --- /dev/null +++ b/src/ClaudeDo.Ui/Views/Modals/AboutModalView.axaml @@ -0,0 +1,49 @@ + + + + + + + + + +