refactor(ui): dedupe path-open helper across five view models
ListsIslandViewModel.OpenInExplorer, MergeSectionViewModel.OpenWorktree,
WorktreesOverviewModalViewModel.OpenInExplorer, AboutModalViewModel.OpenPath and
TasksIslandViewModel.OpenTaskWorktree each reimplemented "open this path in the
shell" with their own existence check, launch mechanism and error handling.
Replace all five with the new ShellOpen.Path helper and route failures through
the existing ErrorReported -> footer-strip convention instead of bare catch {}.
This commit is contained in:
@@ -339,6 +339,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
AgentSettings = new AgentConfigEditorViewModel(worker, AgentConfigScope.Task);
|
||||
Merge = new MergeSectionViewModel(worker, services);
|
||||
Merge.DiffViewed = () => ReviewDiffViewed = true;
|
||||
Merge.ErrorReported += msg => ErrorReported?.Invoke(msg);
|
||||
Merge.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(MergeSectionViewModel.HasReviewableDiff))
|
||||
|
||||
@@ -80,6 +80,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
var rawId = row.Id.StartsWith("user:", StringComparison.Ordinal) ? row.Id["user:".Length..] : row.Id;
|
||||
var vm = _services.GetRequiredService<WorktreesOverviewModalViewModel>();
|
||||
vm.ErrorReported += msg => ErrorReported?.Invoke(msg);
|
||||
vm.Configure(rawId, row.Name);
|
||||
await vm.LoadAsync();
|
||||
await Dialogs.ShowWorktreesOverviewAsync(vm);
|
||||
@@ -107,17 +108,9 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
[RelayCommand]
|
||||
private void OpenInExplorer(ListNavItemViewModel? row)
|
||||
{
|
||||
var dir = row?.WorkingDir;
|
||||
if (string.IsNullOrWhiteSpace(dir) || !System.IO.Directory.Exists(dir)) return;
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = dir,
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch { /* best-effort */ }
|
||||
var (ok, error) = ShellOpen.Path(row?.WorkingDir);
|
||||
if (!ok && error is not null)
|
||||
ErrorReported?.Invoke(Loc.T("vm.lists.openInExplorerFailed", error));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -60,6 +60,10 @@ public sealed partial class MergeSectionViewModel : ViewModelBase
|
||||
// review gate can record that the changes were inspected before merging.
|
||||
public Action? DiffViewed { get; set; }
|
||||
|
||||
// Mirrors ListsIslandViewModel/TasksIslandViewModel.ErrorReported — DetailsIslandViewModel
|
||||
// forwards this into its own ErrorReported, which the shell flashes in the footer strip.
|
||||
public event Action<string>? ErrorReported;
|
||||
|
||||
// True when there is something to inspect before merging (a live worktree diff,
|
||||
// a merged commit range, or a planning/children combined diff). When false there
|
||||
// is nothing to read, so the review gate must not block approve.
|
||||
@@ -191,16 +195,9 @@ public sealed partial class MergeSectionViewModel : ViewModelBase
|
||||
[RelayCommand(CanExecute = nameof(CanOpenWorktree))]
|
||||
private void OpenWorktree()
|
||||
{
|
||||
if (_worktreePath is null) return;
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = _worktreePath,
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
var (ok, error) = ShellOpen.Path(_worktreePath);
|
||||
if (!ok && error is not null)
|
||||
ErrorReported?.Invoke(Loc.T("vm.mergeSection.openWorktreeFailed", error));
|
||||
}
|
||||
|
||||
private bool CanOpenWorktree() => _worktreePath != null;
|
||||
|
||||
@@ -1432,16 +1432,9 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
||||
[RelayCommand]
|
||||
private void OpenTaskWorktree(TaskRowViewModel? row)
|
||||
{
|
||||
if (row?.WorktreePath is not { } path || string.IsNullOrWhiteSpace(path)) return;
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("vm.tasksIsland.openWorktreeFailed", ex.Message)); }
|
||||
var (ok, error) = ShellOpen.Path(row?.WorktreePath);
|
||||
if (!ok && error is not null)
|
||||
ErrorReported?.Invoke(Loc.T("vm.tasksIsland.openWorktreeFailed", error));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -544,6 +544,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
private async Task OpenAbout()
|
||||
{
|
||||
var vm = new AboutModalViewModel();
|
||||
vm.ErrorReported += FlashFooterError;
|
||||
if (Dialogs is not null) await Dialogs.ShowAboutAsync(vm);
|
||||
}
|
||||
|
||||
@@ -599,6 +600,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
try
|
||||
{
|
||||
var vm = _worktreesOverviewVmFactory();
|
||||
vm.ErrorReported += FlashFooterError;
|
||||
vm.Configure(null, null);
|
||||
await vm.LoadAsync();
|
||||
await Dialogs.ShowWorktreesOverviewAsync(vm);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
@@ -17,18 +18,17 @@ public sealed partial class AboutModalViewModel : ViewModelBase
|
||||
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
// Mirrors UsageMonitorModalViewModel.ErrorReported — this modal's own window sits in front
|
||||
// of the footer strip, so the caller wires this into FlashFooterError.
|
||||
public event Action<string>? ErrorReported;
|
||||
|
||||
[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 */ }
|
||||
var (ok, error) = ShellOpen.Path(path);
|
||||
if (!ok && error is not null)
|
||||
ErrorReported?.Invoke(Loc.T("vm.about.openPathFailed", error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Input.Platform;
|
||||
@@ -94,6 +93,10 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
public ObservableCollection<string> MergeTargets { get; } = new();
|
||||
public ObservableCollection<WorktreeOverviewRowViewModel> ConflictRows { get; } = new();
|
||||
|
||||
// Mirrors UsageMonitorModalViewModel.ErrorReported — surfaces modal-owned failures in the
|
||||
// footer strip once the caller wires it (the modal's own window sits in front of the strip).
|
||||
public event Action<string>? ErrorReported;
|
||||
|
||||
public Action? CloseAction { get; set; }
|
||||
public Action<DiffViewerViewModel>? ShowDiffAction { get; set; }
|
||||
public Action<string, string>? JumpToTaskAction { get; set; }
|
||||
@@ -262,9 +265,9 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
[RelayCommand]
|
||||
private void OpenInExplorer(WorktreeOverviewRowViewModel? row)
|
||||
{
|
||||
if (row is null || !row.PathExistsOnDisk) return;
|
||||
try { Process.Start(new ProcessStartInfo { FileName = row.Path, UseShellExecute = true }); }
|
||||
catch { }
|
||||
var (ok, error) = ShellOpen.Path(row?.Path);
|
||||
if (!ok && error is not null)
|
||||
ErrorReported?.Invoke(Loc.T("vm.worktreesOverview.openInExplorerFailed", error));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
Reference in New Issue
Block a user