feat(06-02): add global site selection state, command, and broadcast to MainWindowViewModel

- Add OpenGlobalSitePickerDialog factory property (dialog factory pattern)
- Add GlobalSelectedSites ObservableCollection<SiteInfo>
- Add GlobalSitesSelectedLabel computed property for toolbar display
- Add OpenGlobalSitePickerCommand (disabled when no profile selected)
- Broadcast GlobalSitesChangedMessage via WeakReferenceMessenger on collection change
- Clear GlobalSelectedSites on tenant switch (OnSelectedProfileChanged)
- Clear GlobalSelectedSites on session clear (ClearSessionAsync)
- Add using SharepointToolbox.Views.Dialogs for SitePickerDialog cast
This commit is contained in:
Dev
2026-04-07 10:03:30 +02:00
parent 7874fa8524
commit a10f03edc8

View File

@@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using SharepointToolbox.Core.Messages; using SharepointToolbox.Core.Messages;
using SharepointToolbox.Core.Models; using SharepointToolbox.Core.Models;
using SharepointToolbox.Services; using SharepointToolbox.Services;
using SharepointToolbox.Views.Dialogs;
namespace SharepointToolbox.ViewModels; namespace SharepointToolbox.ViewModels;
@@ -19,6 +20,12 @@ public partial class MainWindowViewModel : ObservableRecipient
// Set by the view layer (MainWindow.xaml.cs) to open the dialog using DI // Set by the view layer (MainWindow.xaml.cs) to open the dialog using DI
public Func<Window>? OpenProfileManagementDialog { get; set; } public Func<Window>? OpenProfileManagementDialog { get; set; }
/// <summary>
/// Factory set by MainWindow.xaml.cs to open the SitePickerDialog for global site selection.
/// Returns the opened Window; ViewModel calls ShowDialog() on it.
/// </summary>
public Func<Window>? OpenGlobalSitePickerDialog { get; set; }
[ObservableProperty] [ObservableProperty]
private TenantProfile? _selectedProfile; private TenantProfile? _selectedProfile;
@@ -33,9 +40,20 @@ public partial class MainWindowViewModel : ObservableRecipient
public ObservableCollection<TenantProfile> TenantProfiles { get; } = new(); public ObservableCollection<TenantProfile> TenantProfiles { get; } = new();
public ObservableCollection<SiteInfo> GlobalSelectedSites { get; } = new();
/// <summary>
/// Label for toolbar display: "3 site(s) selected" or "No sites selected".
/// </summary>
public string GlobalSitesSelectedLabel =>
GlobalSelectedSites.Count > 0
? $"{GlobalSelectedSites.Count} site(s) selected"
: "No sites selected";
public IAsyncRelayCommand ConnectCommand { get; } public IAsyncRelayCommand ConnectCommand { get; }
public IAsyncRelayCommand ClearSessionCommand { get; } public IAsyncRelayCommand ClearSessionCommand { get; }
public RelayCommand ManageProfilesCommand { get; } public RelayCommand ManageProfilesCommand { get; }
public RelayCommand OpenGlobalSitePickerCommand { get; }
public MainWindowViewModel( public MainWindowViewModel(
ProfileService profileService, ProfileService profileService,
@@ -49,6 +67,12 @@ public partial class MainWindowViewModel : ObservableRecipient
ConnectCommand = new AsyncRelayCommand(ConnectAsync, () => SelectedProfile != null); ConnectCommand = new AsyncRelayCommand(ConnectAsync, () => SelectedProfile != null);
ClearSessionCommand = new AsyncRelayCommand(ClearSessionAsync, () => SelectedProfile != null); ClearSessionCommand = new AsyncRelayCommand(ClearSessionAsync, () => SelectedProfile != null);
ManageProfilesCommand = new RelayCommand(OpenProfileManagement); ManageProfilesCommand = new RelayCommand(OpenProfileManagement);
OpenGlobalSitePickerCommand = new RelayCommand(ExecuteOpenGlobalSitePicker, () => SelectedProfile != null);
GlobalSelectedSites.CollectionChanged += (_, _) =>
{
OnPropertyChanged(nameof(GlobalSitesSelectedLabel));
BroadcastGlobalSites();
};
IsActive = true; IsActive = true;
} }
@@ -74,6 +98,9 @@ public partial class MainWindowViewModel : ObservableRecipient
} }
ConnectCommand.NotifyCanExecuteChanged(); ConnectCommand.NotifyCanExecuteChanged();
ClearSessionCommand.NotifyCanExecuteChanged(); ClearSessionCommand.NotifyCanExecuteChanged();
// Clear global site selection on tenant switch (sites belong to a tenant)
GlobalSelectedSites.Clear();
OpenGlobalSitePickerCommand.NotifyCanExecuteChanged();
} }
public async Task LoadProfilesAsync() public async Task LoadProfilesAsync()
@@ -116,6 +143,7 @@ public partial class MainWindowViewModel : ObservableRecipient
try try
{ {
await _sessionManager.ClearSessionAsync(SelectedProfile.TenantUrl); await _sessionManager.ClearSessionAsync(SelectedProfile.TenantUrl);
GlobalSelectedSites.Clear();
ConnectionStatus = "Not connected"; ConnectionStatus = "Not connected";
} }
catch (Exception ex) catch (Exception ex)
@@ -134,4 +162,22 @@ public partial class MainWindowViewModel : ObservableRecipient
// Reload profiles after dialog closes (modal — ShowDialog blocks until closed) // Reload profiles after dialog closes (modal — ShowDialog blocks until closed)
_ = LoadProfilesAsync(); _ = LoadProfilesAsync();
} }
private void ExecuteOpenGlobalSitePicker()
{
if (OpenGlobalSitePickerDialog == null) return;
var dialog = OpenGlobalSitePickerDialog.Invoke();
if (dialog?.ShowDialog() == true && dialog is SitePickerDialog picker)
{
GlobalSelectedSites.Clear();
foreach (var site in picker.SelectedUrls)
GlobalSelectedSites.Add(site);
}
}
private void BroadcastGlobalSites()
{
WeakReferenceMessenger.Default.Send(
new GlobalSitesChangedMessage(GlobalSelectedSites.ToList().AsReadOnly()));
}
} }