chore: release v2.4
- Add theme system (Dark/Light palettes, ModernTheme, ThemeManager) - Add InputDialog, Spinner common view - Add DuplicatesCsvExportService - Refresh views, dialogs, and view models across tabs - Update localization strings (en/fr) - Tweak services (transfer, permissions, search, user access, ownership elevation, bulk operations) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Win32;
|
||||
using SharepointToolbox.Core.Models;
|
||||
using SharepointToolbox.Localization;
|
||||
using SharepointToolbox.Services;
|
||||
using SharepointToolbox.Services.Export;
|
||||
|
||||
@@ -31,6 +32,7 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
|
||||
private readonly IDuplicatesService _duplicatesService;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly DuplicatesHtmlExportService _htmlExportService;
|
||||
private readonly DuplicatesCsvExportService _csvExportService;
|
||||
private readonly IBrandingService _brandingService;
|
||||
private readonly ILogger<FeatureViewModelBase> _logger;
|
||||
private TenantProfile? _currentProfile;
|
||||
@@ -46,6 +48,14 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
|
||||
[ObservableProperty] private bool _includeSubsites;
|
||||
[ObservableProperty] private string _library = string.Empty;
|
||||
|
||||
/// <summary>0 = Single file, 1 = Split by site.</summary>
|
||||
[ObservableProperty] private int _splitModeIndex;
|
||||
/// <summary>0 = Separate HTML files, 1 = Single tabbed HTML.</summary>
|
||||
[ObservableProperty] private int _htmlLayoutIndex;
|
||||
|
||||
private ReportSplitMode CurrentSplit => SplitModeIndex == 1 ? ReportSplitMode.BySite : ReportSplitMode.Single;
|
||||
private HtmlSplitLayout CurrentLayout => HtmlLayoutIndex == 1 ? HtmlSplitLayout.SingleTabbed : HtmlSplitLayout.SeparateFiles;
|
||||
|
||||
private ObservableCollection<DuplicateRow> _results = new();
|
||||
public ObservableCollection<DuplicateRow> Results
|
||||
{
|
||||
@@ -55,16 +65,19 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
|
||||
_results = value;
|
||||
OnPropertyChanged();
|
||||
ExportHtmlCommand.NotifyCanExecuteChanged();
|
||||
ExportCsvCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand ExportHtmlCommand { get; }
|
||||
public IAsyncRelayCommand ExportCsvCommand { get; }
|
||||
public TenantProfile? CurrentProfile => _currentProfile;
|
||||
|
||||
public DuplicatesViewModel(
|
||||
IDuplicatesService duplicatesService,
|
||||
ISessionManager sessionManager,
|
||||
DuplicatesHtmlExportService htmlExportService,
|
||||
DuplicatesCsvExportService csvExportService,
|
||||
IBrandingService brandingService,
|
||||
ILogger<FeatureViewModelBase> logger)
|
||||
: base(logger)
|
||||
@@ -72,24 +85,26 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
|
||||
_duplicatesService = duplicatesService;
|
||||
_sessionManager = sessionManager;
|
||||
_htmlExportService = htmlExportService;
|
||||
_csvExportService = csvExportService;
|
||||
_brandingService = brandingService;
|
||||
_logger = logger;
|
||||
|
||||
ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
|
||||
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
|
||||
}
|
||||
|
||||
protected override async Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
|
||||
{
|
||||
if (_currentProfile == null)
|
||||
{
|
||||
StatusMessage = "No tenant selected. Please connect to a tenant first.";
|
||||
StatusMessage = TranslationSource.Instance["err.no_tenant_connected"];
|
||||
return;
|
||||
}
|
||||
|
||||
var urls = GlobalSites.Select(s => s.Url).Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
|
||||
if (urls.Count == 0)
|
||||
{
|
||||
StatusMessage = "Select at least one site from the toolbar.";
|
||||
StatusMessage = TranslationSource.Instance["err.no_sites_selected"];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,6 +167,7 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
|
||||
_lastGroups = Array.Empty<DuplicateGroup>();
|
||||
OnPropertyChanged(nameof(CurrentProfile));
|
||||
ExportHtmlCommand.NotifyCanExecuteChanged();
|
||||
ExportCsvCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
internal void SetCurrentProfile(TenantProfile profile) => _currentProfile = profile;
|
||||
@@ -179,9 +195,28 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
|
||||
branding = new ReportBranding(mspLogo, clientLogo);
|
||||
}
|
||||
|
||||
await _htmlExportService.WriteAsync(_lastGroups, dialog.FileName, CancellationToken.None, branding);
|
||||
await _htmlExportService.WriteAsync(_lastGroups, dialog.FileName, CurrentSplit, CurrentLayout, CancellationToken.None, branding);
|
||||
Process.Start(new ProcessStartInfo(dialog.FileName) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); }
|
||||
}
|
||||
|
||||
private async Task ExportCsvAsync()
|
||||
{
|
||||
if (_lastGroups.Count == 0) return;
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Export duplicates report to CSV",
|
||||
Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*",
|
||||
DefaultExt = "csv",
|
||||
FileName = "duplicates_report"
|
||||
};
|
||||
if (dialog.ShowDialog() != true) return;
|
||||
try
|
||||
{
|
||||
await _csvExportService.WriteAsync(_lastGroups, dialog.FileName, CurrentSplit, CancellationToken.None);
|
||||
Process.Start(new ProcessStartInfo(dialog.FileName) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "CSV export failed."); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user