diff --git a/src/ClaudeDo.Ui/Services/IDialogService.cs b/src/ClaudeDo.Ui/Services/IDialogService.cs
new file mode 100644
index 0000000..94fe0a2
--- /dev/null
+++ b/src/ClaudeDo.Ui/Services/IDialogService.cs
@@ -0,0 +1,30 @@
+using System.Threading.Tasks;
+using ClaudeDo.Ui.ViewModels.Conflicts;
+using ClaudeDo.Ui.ViewModels.Modals;
+
+namespace ClaudeDo.Ui.Services;
+
+///
+/// Single seam for opening modal dialogs. Replaces the per-modal Show*Modal
+/// Func callbacks that were previously wired separately on the shell and the lists
+/// island (and the Confirm/Error dialogs duplicated in both code-behinds). The view
+/// layer supplies the implementation ();
+/// callers build + initialize the VM and hand it here to be shown.
+///
+public interface IDialogService
+{
+ Task ShowAboutAsync(AboutModalViewModel vm);
+ Task ShowWeeklyReportAsync(WeeklyReportModalViewModel vm);
+ Task ShowSettingsAsync(SettingsModalViewModel vm);
+ Task ShowListSettingsAsync(ListSettingsModalViewModel vm);
+ Task ShowRepoImportAsync(RepoImportModalViewModel vm);
+ Task ShowWorktreesOverviewAsync(WorktreesOverviewModalViewModel vm);
+ Task ShowWorkerConnectionAsync(WorkerConnectionModalViewModel vm);
+ Task ShowConflictResolverAsync(ConflictResolverViewModel vm);
+
+ /// Modal yes/no confirmation. Returns true only when confirmed.
+ Task ConfirmAsync(string message);
+
+ /// Modal error notice with a single dismiss button.
+ Task ShowErrorAsync(string message);
+}
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
index f5ad258..5ddba2e 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
@@ -27,28 +27,25 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
public event EventHandler? FocusSearchRequested;
public void RequestFocusSearch() => FocusSearchRequested?.Invoke(this, EventArgs.Empty);
- public Func? ShowSettingsModal { get; set; }
- public Func? ShowListSettingsModal { get; set; }
- public Func? ShowWorktreesOverviewModal { get; set; }
- public Func? ShowRepoImportModal { get; set; }
+ public IDialogService? Dialogs { get; set; }
[RelayCommand]
private async Task OpenSettings()
{
- if (ShowSettingsModal is null || _services is null) return;
+ if (Dialogs is null || _services is null) return;
var settingsVm = _services.GetRequiredService();
await settingsVm.LoadAsync();
- await ShowSettingsModal(settingsVm);
+ await Dialogs.ShowSettingsAsync(settingsVm);
}
[RelayCommand]
private async System.Threading.Tasks.Task OpenListSettingsAsync(ListNavItemViewModel? row)
{
- if (row is null || ShowListSettingsModal is null || _services is null) return;
+ if (row is null || Dialogs is null || _services is null) return;
var rawId = row.Id.StartsWith("user:", StringComparison.Ordinal) ? row.Id["user:".Length..] : row.Id;
var vm = _services.GetRequiredService();
await vm.LoadAsync(rawId, row.Name, row.WorkingDir, row.DefaultCommitType);
- await ShowListSettingsModal(vm);
+ await Dialogs.ShowListSettingsAsync(vm);
if (vm.Deleted) await LoadAsync();
else await RefreshRowAsync(row.Id);
}
@@ -56,10 +53,10 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
[RelayCommand]
private async System.Threading.Tasks.Task OpenRepoImportAsync()
{
- if (ShowRepoImportModal is null || _services is null) return;
+ if (Dialogs is null || _services is null) return;
var vm = _services.GetRequiredService();
await vm.LoadAsync();
- await ShowRepoImportModal(vm);
+ await Dialogs.ShowRepoImportAsync(vm);
await LoadAsync();
}
@@ -68,7 +65,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
[RelayCommand]
private async Task OpenWorktreesOverviewAsync(ListNavItemViewModel? row)
{
- if (row is null || ShowWorktreesOverviewModal is null || _services is null) return;
+ if (row is null || Dialogs is null || _services is null) return;
if (row.Kind != ListKind.User) return;
if (_worktreesOverviewOpen) return;
_worktreesOverviewOpen = true;
@@ -78,7 +75,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
var vm = _services.GetRequiredService();
vm.Configure(rawId, row.Name);
await vm.LoadAsync();
- await ShowWorktreesOverviewModal(vm);
+ await Dialogs.ShowWorktreesOverviewAsync(vm);
}
finally { _worktreesOverviewOpen = false; }
}
@@ -297,11 +294,11 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
UserLists.Add(item);
SelectedList = item;
- if (ShowListSettingsModal is not null && _services is not null)
+ if (Dialogs is not null && _services is not null)
{
var vm = _services.GetRequiredService();
await vm.LoadAsync(entity.Id, entity.Name, entity.WorkingDir, entity.DefaultCommitType);
- await ShowListSettingsModal(vm);
+ await Dialogs.ShowListSettingsAsync(vm);
if (vm.Deleted) await LoadAsync();
else await RefreshRowAsync(item.Id);
}
diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
index 41287d4..29576c9 100644
--- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs
@@ -41,35 +41,30 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
public Func ResolveMergeVm => _mergeVmFactory;
- // Layer C seam: composition root sets the factory; MainWindow sets the dialog opener.
- // The integrator connects Layer A/B's RequestConflictResolution(taskId, target) to this method.
+ // Layer C seam: composition root sets the factory; the dialog service shows the resolver.
public Func? ConflictResolverFactory { get; set; }
- public Func? ShowConflictResolver { get; set; }
+
+ // Single dialog seam (set by MainWindow); propagated to the lists island.
+ private IDialogService? _dialogs;
+ public IDialogService? Dialogs
+ {
+ get => _dialogs;
+ set
+ {
+ _dialogs = value;
+ if (Lists is not null) Lists.Dialogs = value;
+ }
+ }
public async Task RequestConflictResolutionAsync(string taskId, string targetBranch)
{
- if (ConflictResolverFactory is null || ShowConflictResolver is null) return;
+ if (ConflictResolverFactory is null || Dialogs is null) return;
var vm = ConflictResolverFactory(taskId);
var hasConflicts = await vm.OpenAsync(targetBranch);
if (hasConflicts)
- await ShowConflictResolver(vm);
+ await Dialogs.ShowConflictResolverAsync(vm);
}
- // Set by MainWindow to open the About dialog.
- public Func? ShowAboutModal { get; set; }
-
- // Set by MainWindow to open the repo-import dialog.
- public Func? ShowRepoImportModal { get; set; }
-
- // Set by MainWindow to open the global worktrees overview dialog.
- public Func? ShowWorktreesOverviewModal { get; set; }
-
- // Set by MainWindow to open the weekly report dialog.
- public Func? ShowWeeklyReportModal { get; set; }
-
- // Set by MainWindow to open the worker-connection help dialog.
- public Func? ShowWorkerConnectionModal { get; set; }
-
[ObservableProperty] private bool _isUpdateBannerVisible;
[ObservableProperty] private string? _updateBannerLatestVersion;
[ObservableProperty] private string? _inlineUpdateStatus;
@@ -149,11 +144,11 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
private async Task OpenPlanningConflictAsync(string planningTaskId, string subtaskId)
{
- if (ConflictResolverFactory is null || ShowConflictResolver is null) return;
+ if (ConflictResolverFactory is null || Dialogs is null) return;
var vm = ConflictResolverFactory(subtaskId);
var hasConflicts = await vm.OpenForPlanningAsync(planningTaskId, subtaskId);
if (hasConflicts)
- await ShowConflictResolver(vm);
+ await Dialogs.ShowConflictResolverAsync(vm);
}
// For tests only — does NOT wire up events.
@@ -281,7 +276,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
private async Task OpenAbout()
{
var vm = new AboutModalViewModel();
- if (ShowAboutModal is not null) await ShowAboutModal(vm);
+ if (Dialogs is not null) await Dialogs.ShowAboutAsync(vm);
}
private bool _connectionPromptShown;
@@ -297,7 +292,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
private async Task OpenWorkerConnectionHelpAsync()
{
var vm = new WorkerConnectionModalViewModel(_workerLocator, _installerLocator);
- if (ShowWorkerConnectionModal is not null) await ShowWorkerConnectionModal(vm);
+ if (Dialogs is not null) await Dialogs.ShowWorkerConnectionAsync(vm);
}
[RelayCommand]
@@ -306,10 +301,10 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
[RelayCommand]
private async Task OpenRepoImport()
{
- if (ShowRepoImportModal is null || _repoImportVmFactory is null) return;
+ if (Dialogs is null || _repoImportVmFactory is null) return;
var vm = _repoImportVmFactory();
await vm.LoadAsync();
- await ShowRepoImportModal(vm);
+ await Dialogs.ShowRepoImportAsync(vm);
if (Lists is not null) await Lists.LoadAsync();
}
@@ -318,14 +313,14 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
[RelayCommand]
private async Task OpenWorktreesOverviewGlobalAsync()
{
- if (ShowWorktreesOverviewModal is null || _worktreesOverviewOpen) return;
+ if (Dialogs is null || _worktreesOverviewOpen) return;
_worktreesOverviewOpen = true;
try
{
var vm = _worktreesOverviewVmFactory();
vm.Configure(null, null);
await vm.LoadAsync();
- await ShowWorktreesOverviewModal(vm);
+ await Dialogs.ShowWorktreesOverviewAsync(vm);
}
finally { _worktreesOverviewOpen = false; }
}
@@ -335,13 +330,13 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
[RelayCommand]
private async Task OpenWeeklyReport()
{
- if (ShowWeeklyReportModal is null || _weeklyReportOpen) return;
+ if (Dialogs is null || _weeklyReportOpen) return;
_weeklyReportOpen = true;
try
{
var vm = _weeklyReportVmFactory();
await vm.InitializeAsync();
- await ShowWeeklyReportModal(vm);
+ await Dialogs.ShowWeeklyReportAsync(vm);
}
finally { _weeklyReportOpen = false; }
}
diff --git a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs
index 3bbadd7..2a886dc 100644
--- a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs
+++ b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs
@@ -25,61 +25,7 @@ public partial class ListsIslandView : UserControl
DataContextChanged += (_, _) =>
{
if (DataContext is ListsIslandViewModel vm)
- {
vm.FocusSearchRequested += (_, _) => SearchBox.Focus();
- vm.ShowSettingsModal = ShowSettingsAsync;
- vm.ShowListSettingsModal = async modal =>
- {
- var window = new ListSettingsModalView { DataContext = modal };
- modal.CloseAction = () => window.Close();
- modal.ConfirmAsync = ShowConfirmAsync;
- modal.ShowErrorAsync = ShowErrorDialogAsync;
- var top = TopLevel.GetTopLevel(this) as Window;
- if (top is null) window.Show();
- else await window.ShowDialog(top);
- };
- vm.ShowRepoImportModal = async modal =>
- {
- var window = new RepoImportModalView { DataContext = modal };
- modal.CloseAction = () => window.Close();
- var top = TopLevel.GetTopLevel(this) as Window;
- if (top is null) window.Show();
- else await window.ShowDialog(top);
- };
- vm.ShowWorktreesOverviewModal = async modal =>
- {
- var top = TopLevel.GetTopLevel(this) as Window;
- var shell = top?.DataContext as IslandsShellViewModel;
- var window = new WorktreesOverviewModalView { DataContext = modal };
- modal.CloseAction = () => window.Close();
- modal.JumpToTaskAction = (listId, taskId) =>
- {
- if (shell is not null)
- _ = JumpToTaskAsync(shell, listId, taskId);
- };
- modal.ShowDiffAction = diffVm =>
- {
- if (top is null) return;
- var dlg = new WorktreeModalView { DataContext = diffVm };
- diffVm.CloseAction = () => dlg.Close();
- _ = diffVm.LoadAsync();
- _ = dlg.ShowDialog(top);
- };
- modal.ConfirmAction = ShowConfirmAsync;
- if (shell is not null)
- {
- modal.ResolveMergeVm = shell.ResolveMergeVm;
- modal.ShowMergeAction = async mergeVm =>
- {
- if (top is null) return;
- var mergeDlg = new MergeModalView { DataContext = mergeVm };
- await mergeDlg.ShowDialog(top);
- };
- }
- if (top is null) window.Show();
- else await window.ShowDialog(top);
- };
- }
};
}
@@ -211,95 +157,4 @@ public partial class ListsIslandView : UserControl
return idx + 1 < vm.UserLists.Count ? vm.UserLists[idx + 1] : null;
}
- private async System.Threading.Tasks.Task ShowSettingsAsync(SettingsModalViewModel settingsVm)
- {
- var owner = TopLevel.GetTopLevel(this) as Window;
- if (owner == null) return;
- var modal = new SettingsModalView { DataContext = settingsVm };
- await modal.ShowDialog(owner);
- }
-
- private static System.Threading.Tasks.Task JumpToTaskAsync(IslandsShellViewModel s, string listId, string taskId)
- => JumpToTaskHelper.SelectAsync(s, listId, taskId);
-
- private async System.Threading.Tasks.Task ShowErrorDialogAsync(string message)
- {
- var owner = TopLevel.GetTopLevel(this) as Window;
- if (owner is null) return;
-
- var ok = new Button { Content = "OK", MinWidth = 90 };
- var dialog = new Window
- {
- Title = "Error",
- Width = 360,
- SizeToContent = SizeToContent.Height,
- CanResize = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- ShowInTaskbar = false,
- Background = this.FindResource("SurfaceBrush") as IBrush,
- Content = new StackPanel
- {
- Spacing = 16,
- Margin = new Thickness(20),
- Children =
- {
- new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap },
- new StackPanel
- {
- Orientation = Orientation.Horizontal,
- Spacing = 8,
- HorizontalAlignment = HorizontalAlignment.Right,
- Children = { ok },
- },
- },
- },
- };
-
- ok.Click += (_, _) => dialog.Close();
- await dialog.ShowDialog(owner);
- }
-
- private async System.Threading.Tasks.Task ShowConfirmAsync(string message)
- {
- var owner = TopLevel.GetTopLevel(this) as Window;
- if (owner is null) return false;
-
- var tcs = new TaskCompletionSource();
- var cancel = new Button { Content = "Cancel", MinWidth = 90 };
- var confirm = new Button { Content = "Confirm", MinWidth = 90, Classes = { "danger" } };
-
- var dialog = new Window
- {
- Title = "Confirm",
- Width = 380,
- SizeToContent = SizeToContent.Height,
- CanResize = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- ShowInTaskbar = false,
- Background = this.FindResource("SurfaceBrush") as IBrush,
- Content = new StackPanel
- {
- Margin = new Thickness(20),
- Spacing = 16,
- Children =
- {
- new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap },
- new StackPanel
- {
- Orientation = Orientation.Horizontal,
- HorizontalAlignment = HorizontalAlignment.Right,
- Spacing = 8,
- Children = { cancel, confirm },
- },
- },
- },
- };
-
- cancel.Click += (_, _) => { tcs.TrySetResult(false); dialog.Close(); };
- confirm.Click += (_, _) => { tcs.TrySetResult(true); dialog.Close(); };
- dialog.Closed += (_, _) => tcs.TrySetResult(false);
-
- _ = dialog.ShowDialog(owner);
- return await tcs.Task;
- }
}
diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs
index f340dad..8536ee8 100644
--- a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs
+++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs
@@ -42,65 +42,7 @@ public partial class MainWindow : Window
{
if (DataContext is IslandsShellViewModel vm)
{
- 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);
- };
- vm.ShowWeeklyReportModal = async (modal) =>
- {
- var dlg = new WeeklyReportModalView { DataContext = modal };
- modal.CloseAction = () => dlg.Close();
- await dlg.ShowDialog(this);
- };
- vm.ShowWorktreesOverviewModal = async (modal) =>
- {
- var dlg = new WorktreesOverviewModalView { DataContext = modal };
- modal.CloseAction = () => dlg.Close();
- modal.JumpToTaskAction = (listId, taskId) =>
- {
- if (DataContext is IslandsShellViewModel s)
- _ = JumpToTaskAsync(s, listId, taskId);
- };
- modal.ShowDiffAction = diffVm =>
- {
- var diffDlg = new WorktreeModalView { DataContext = diffVm };
- diffVm.CloseAction = () => diffDlg.Close();
- _ = diffVm.LoadAsync();
- _ = diffDlg.ShowDialog(this);
- };
- modal.ConfirmAction = ShowConfirmAsync;
- modal.ResolveMergeVm = vm.ResolveMergeVm;
- modal.ShowMergeAction = async mergeVm =>
- {
- var mergeDlg = new MergeModalView { DataContext = mergeVm };
- await mergeDlg.ShowDialog(this);
- };
- modal.RequestConflictResolution = (taskId, target) =>
- DataContext is IslandsShellViewModel s
- ? s.RequestConflictResolutionAsync(taskId, target)
- : System.Threading.Tasks.Task.CompletedTask;
- await dlg.ShowDialog(this);
- };
- vm.ShowRepoImportModal = async (modal) =>
- {
- var dlg = new RepoImportModalView { DataContext = modal };
- modal.CloseAction = () => dlg.Close();
- await dlg.ShowDialog(this);
- };
- vm.ShowWorkerConnectionModal = async (connVm) =>
- {
- var dlg = new WorkerConnectionModalView { DataContext = connVm };
- connVm.CloseAction = () => dlg.Close();
- await dlg.ShowDialog(this);
- };
- vm.ShowConflictResolver = async (resolverVm) =>
- {
- var dlg = new ClaudeDo.Ui.Views.Conflicts.ConflictResolverView { DataContext = resolverVm };
- await dlg.ShowDialog(this);
- };
+ vm.Dialogs = new WindowDialogService(this);
}
}
@@ -132,47 +74,4 @@ public partial class MainWindow : Window
if (DataContext is IslandsShellViewModel vm) vm.WindowWidth = Bounds.Width;
}
- private static System.Threading.Tasks.Task JumpToTaskAsync(IslandsShellViewModel s, string listId, string taskId)
- => JumpToTaskHelper.SelectAsync(s, listId, taskId);
-
- private async System.Threading.Tasks.Task ShowConfirmAsync(string message)
- {
- var tcs = new TaskCompletionSource();
- var cancel = new Button { Content = "Cancel", MinWidth = 90 };
- var confirm = new Button { Content = "Confirm", MinWidth = 90, Classes = { "danger" } };
-
- var dialog = new Window
- {
- Title = "Confirm",
- Width = 380,
- SizeToContent = SizeToContent.Height,
- CanResize = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- ShowInTaskbar = false,
- Background = this.FindResource("SurfaceBrush") as IBrush,
- Content = new StackPanel
- {
- Margin = new Thickness(20),
- Spacing = 16,
- Children =
- {
- new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap },
- new StackPanel
- {
- Orientation = Orientation.Horizontal,
- HorizontalAlignment = HorizontalAlignment.Right,
- Spacing = 8,
- Children = { cancel, confirm },
- },
- },
- },
- };
-
- cancel.Click += (_, _) => { tcs.TrySetResult(false); dialog.Close(); };
- confirm.Click += (_, _) => { tcs.TrySetResult(true); dialog.Close(); };
- dialog.Closed += (_, _) => tcs.TrySetResult(false);
-
- _ = dialog.ShowDialog(this);
- return await tcs.Task;
- }
}
diff --git a/src/ClaudeDo.Ui/Views/WindowDialogService.cs b/src/ClaudeDo.Ui/Views/WindowDialogService.cs
new file mode 100644
index 0000000..8c4f70d
--- /dev/null
+++ b/src/ClaudeDo.Ui/Views/WindowDialogService.cs
@@ -0,0 +1,167 @@
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.Media;
+using ClaudeDo.Ui.Services;
+using ClaudeDo.Ui.ViewModels;
+using ClaudeDo.Ui.ViewModels.Conflicts;
+using ClaudeDo.Ui.ViewModels.Modals;
+using ClaudeDo.Ui.Views.Conflicts;
+using ClaudeDo.Ui.Views.Modals;
+
+namespace ClaudeDo.Ui.Views;
+
+///
+/// Window-backed . Owns every modal-view construction, the
+/// shared Confirm/Error dialogs, and the worktrees-overview sub-callbacks that reach back
+/// into the shell (jump-to-task, merge, conflict resolution). Created by
+/// with itself as the owner window; the shell resolves lazily from the owner's DataContext so
+/// the service and shell don't form a construction cycle.
+///
+public sealed class WindowDialogService : IDialogService
+{
+ private readonly Window _owner;
+
+ public WindowDialogService(Window owner) => _owner = owner;
+
+ private IslandsShellViewModel? Shell => _owner.DataContext as IslandsShellViewModel;
+
+ public async Task ShowAboutAsync(AboutModalViewModel vm)
+ {
+ var dlg = new AboutModalView { DataContext = vm };
+ vm.CloseAction = () => dlg.Close();
+ await dlg.ShowDialog(_owner);
+ }
+
+ public async Task ShowWeeklyReportAsync(WeeklyReportModalViewModel vm)
+ {
+ var dlg = new WeeklyReportModalView { DataContext = vm };
+ vm.CloseAction = () => dlg.Close();
+ await dlg.ShowDialog(_owner);
+ }
+
+ public async Task ShowSettingsAsync(SettingsModalViewModel vm)
+ {
+ var dlg = new SettingsModalView { DataContext = vm };
+ await dlg.ShowDialog(_owner);
+ }
+
+ public async Task ShowListSettingsAsync(ListSettingsModalViewModel vm)
+ {
+ var dlg = new ListSettingsModalView { DataContext = vm };
+ vm.CloseAction = () => dlg.Close();
+ vm.ConfirmAsync = ConfirmAsync;
+ vm.ShowErrorAsync = ShowErrorAsync;
+ await dlg.ShowDialog(_owner);
+ }
+
+ public async Task ShowRepoImportAsync(RepoImportModalViewModel vm)
+ {
+ var dlg = new RepoImportModalView { DataContext = vm };
+ vm.CloseAction = () => dlg.Close();
+ await dlg.ShowDialog(_owner);
+ }
+
+ public async Task ShowWorktreesOverviewAsync(WorktreesOverviewModalViewModel vm)
+ {
+ var dlg = new WorktreesOverviewModalView { DataContext = vm };
+ vm.CloseAction = () => dlg.Close();
+ vm.JumpToTaskAction = (listId, taskId) =>
+ {
+ if (Shell is { } s) _ = JumpToTaskHelper.SelectAsync(s, listId, taskId);
+ };
+ vm.ShowDiffAction = diffVm =>
+ {
+ var diffDlg = new WorktreeModalView { DataContext = diffVm };
+ diffVm.CloseAction = () => diffDlg.Close();
+ _ = diffVm.LoadAsync();
+ _ = diffDlg.ShowDialog(_owner);
+ };
+ vm.ConfirmAction = ConfirmAsync;
+ if (Shell is { } shell)
+ {
+ vm.ResolveMergeVm = shell.ResolveMergeVm;
+ vm.ShowMergeAction = async mergeVm =>
+ {
+ var mergeDlg = new MergeModalView { DataContext = mergeVm };
+ await mergeDlg.ShowDialog(_owner);
+ };
+ vm.RequestConflictResolution = (taskId, target) =>
+ shell.RequestConflictResolutionAsync(taskId, target);
+ }
+ await dlg.ShowDialog(_owner);
+ }
+
+ public async Task ShowWorkerConnectionAsync(WorkerConnectionModalViewModel vm)
+ {
+ var dlg = new WorkerConnectionModalView { DataContext = vm };
+ vm.CloseAction = () => dlg.Close();
+ await dlg.ShowDialog(_owner);
+ }
+
+ public async Task ShowConflictResolverAsync(ConflictResolverViewModel vm)
+ {
+ var dlg = new ConflictResolverView { DataContext = vm };
+ await dlg.ShowDialog(_owner);
+ }
+
+ public Task ConfirmAsync(string message)
+ {
+ var tcs = new TaskCompletionSource();
+ var cancel = new Button { Content = "Cancel", MinWidth = 90 };
+ var confirm = new Button { Content = "Confirm", MinWidth = 90, Classes = { "danger" } };
+
+ var dialog = NoticeWindow("Confirm", 380, message,
+ new StackPanel
+ {
+ Orientation = Orientation.Horizontal,
+ HorizontalAlignment = HorizontalAlignment.Right,
+ Spacing = 8,
+ Children = { cancel, confirm },
+ });
+
+ cancel.Click += (_, _) => { tcs.TrySetResult(false); dialog.Close(); };
+ confirm.Click += (_, _) => { tcs.TrySetResult(true); dialog.Close(); };
+ dialog.Closed += (_, _) => tcs.TrySetResult(false);
+
+ _ = dialog.ShowDialog(_owner);
+ return tcs.Task;
+ }
+
+ public Task ShowErrorAsync(string message)
+ {
+ var ok = new Button { Content = "OK", MinWidth = 90 };
+ var dialog = NoticeWindow("Error", 360, message,
+ new StackPanel
+ {
+ Orientation = Orientation.Horizontal,
+ HorizontalAlignment = HorizontalAlignment.Right,
+ Spacing = 8,
+ Children = { ok },
+ });
+ ok.Click += (_, _) => dialog.Close();
+ return dialog.ShowDialog(_owner);
+ }
+
+ private Window NoticeWindow(string title, double width, string message, StackPanel buttons) => new()
+ {
+ Title = title,
+ Width = width,
+ SizeToContent = SizeToContent.Height,
+ CanResize = false,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ ShowInTaskbar = false,
+ Background = _owner.FindResource("SurfaceBrush") as IBrush,
+ Content = new StackPanel
+ {
+ Margin = new Thickness(20),
+ Spacing = 16,
+ Children =
+ {
+ new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap },
+ buttons,
+ },
+ },
+ };
+}