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:
@@ -22,6 +22,9 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
private readonly StorageCsvExportService _csvExportService;
|
||||
private readonly StorageHtmlExportService _htmlExportService;
|
||||
private readonly IBrandingService? _brandingService;
|
||||
private readonly IOwnershipElevationService? _ownershipService;
|
||||
private readonly SettingsService? _settingsService;
|
||||
private readonly ThemeManager? _themeManager;
|
||||
private readonly ILogger<FeatureViewModelBase> _logger;
|
||||
private TenantProfile? _currentProfile;
|
||||
|
||||
@@ -136,7 +139,10 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
StorageCsvExportService csvExportService,
|
||||
StorageHtmlExportService htmlExportService,
|
||||
IBrandingService brandingService,
|
||||
ILogger<FeatureViewModelBase> logger)
|
||||
ILogger<FeatureViewModelBase> logger,
|
||||
IOwnershipElevationService? ownershipService = null,
|
||||
SettingsService? settingsService = null,
|
||||
ThemeManager? themeManager = null)
|
||||
: base(logger)
|
||||
{
|
||||
_storageService = storageService;
|
||||
@@ -144,10 +150,16 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
_csvExportService = csvExportService;
|
||||
_htmlExportService = htmlExportService;
|
||||
_brandingService = brandingService;
|
||||
_ownershipService = ownershipService;
|
||||
_settingsService = settingsService;
|
||||
_themeManager = themeManager;
|
||||
_logger = logger;
|
||||
|
||||
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
|
||||
ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
|
||||
|
||||
if (_themeManager is not null)
|
||||
_themeManager.ThemeChanged += (_, _) => UpdateChartSeries();
|
||||
}
|
||||
|
||||
/// <summary>Test constructor — omits export services.</summary>
|
||||
@@ -194,6 +206,8 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
var allNodes = new List<StorageNode>();
|
||||
var allTypeMetrics = new List<FileTypeMetric>();
|
||||
|
||||
var autoOwnership = await IsAutoTakeOwnershipEnabled();
|
||||
|
||||
int i = 0;
|
||||
foreach (var url in nonEmpty)
|
||||
{
|
||||
@@ -207,9 +221,30 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
ClientId = _currentProfile.ClientId,
|
||||
Name = _currentProfile.Name
|
||||
};
|
||||
|
||||
var ctx = await _sessionManager.GetOrCreateContextAsync(siteProfile, ct);
|
||||
|
||||
var nodes = await _storageService.CollectStorageAsync(ctx, options, progress, ct);
|
||||
IReadOnlyList<StorageNode> nodes;
|
||||
try
|
||||
{
|
||||
nodes = await _storageService.CollectStorageAsync(ctx, options, progress, ct);
|
||||
}
|
||||
catch (Microsoft.SharePoint.Client.ServerUnauthorizedAccessException) when (_ownershipService != null && autoOwnership)
|
||||
{
|
||||
_logger.LogWarning("Access denied on {Url}, auto-elevating ownership...", url);
|
||||
var adminUrl = DeriveAdminUrl(_currentProfile.TenantUrl ?? url);
|
||||
var adminProfile = new TenantProfile
|
||||
{
|
||||
TenantUrl = adminUrl,
|
||||
ClientId = _currentProfile.ClientId,
|
||||
Name = _currentProfile.Name
|
||||
};
|
||||
var adminCtx = await _sessionManager.GetOrCreateContextAsync(adminProfile, ct);
|
||||
await _ownershipService.ElevateAsync(adminCtx, url, string.Empty, ct);
|
||||
|
||||
ctx = await _sessionManager.GetOrCreateContextAsync(siteProfile, ct);
|
||||
nodes = await _storageService.CollectStorageAsync(ctx, options, progress, ct);
|
||||
}
|
||||
|
||||
// Backfill any libraries where StorageMetrics returned zeros
|
||||
await _storageService.BackfillZeroNodesAsync(ctx, nodes, progress, ct);
|
||||
@@ -258,6 +293,24 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
ExportHtmlCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private async Task<bool> IsAutoTakeOwnershipEnabled()
|
||||
{
|
||||
if (_settingsService == null) return false;
|
||||
var settings = await _settingsService.GetSettingsAsync();
|
||||
return settings.AutoTakeOwnership;
|
||||
}
|
||||
|
||||
internal static string DeriveAdminUrl(string tenantUrl)
|
||||
{
|
||||
var uri = new Uri(tenantUrl.TrimEnd('/'));
|
||||
var host = uri.Host;
|
||||
if (host.Contains("-admin.sharepoint.com", StringComparison.OrdinalIgnoreCase))
|
||||
return tenantUrl;
|
||||
var adminHost = host.Replace(".sharepoint.com", "-admin.sharepoint.com",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
return $"{uri.Scheme}://{adminHost}";
|
||||
}
|
||||
|
||||
internal void SetCurrentProfile(TenantProfile profile) => _currentProfile = profile;
|
||||
|
||||
internal Task TestRunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
|
||||
@@ -324,6 +377,9 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
UpdateChartSeries();
|
||||
}
|
||||
|
||||
private SKColor ChartFgColor => (_themeManager?.IsDarkActive ?? false) ? new SKColor(0xE7, 0xEA, 0xF1) : new SKColor(0x1F, 0x24, 0x30);
|
||||
private SKColor ChartSeparatorColor => (_themeManager?.IsDarkActive ?? false) ? new SKColor(0x32, 0x38, 0x49) : new SKColor(0xE3, 0xE6, 0xEC);
|
||||
|
||||
private void UpdateChartSeries()
|
||||
{
|
||||
var metrics = FileTypeMetrics.ToList();
|
||||
@@ -361,6 +417,7 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
HoverPushout = 8,
|
||||
MaxRadialColumnWidth = 60,
|
||||
DataLabelsFormatter = _ => m.DisplayLabel,
|
||||
DataLabelsPaint = new SolidColorPaint(ChartFgColor),
|
||||
ToolTipLabelFormatter = _ =>
|
||||
$"{m.DisplayLabel}: {FormatBytes(m.TotalSizeBytes)} ({m.FileCount} files)",
|
||||
IsVisibleAtLegend = true,
|
||||
@@ -379,7 +436,8 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
{
|
||||
int idx = (int)point.Index;
|
||||
return idx < chartItems.Count ? FormatBytes(chartItems[idx].TotalSizeBytes) : "";
|
||||
}
|
||||
},
|
||||
DataLabelsPaint = new SolidColorPaint(ChartFgColor)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -388,7 +446,10 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
new Axis
|
||||
{
|
||||
Labels = chartItems.Select(m => m.DisplayLabel).ToArray(),
|
||||
LabelsRotation = -45
|
||||
LabelsRotation = -45,
|
||||
LabelsPaint = new SolidColorPaint(ChartFgColor),
|
||||
TicksPaint = new SolidColorPaint(ChartFgColor),
|
||||
SeparatorsPaint = new SolidColorPaint(ChartSeparatorColor)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -396,7 +457,10 @@ public partial class StorageViewModel : FeatureViewModelBase
|
||||
{
|
||||
new Axis
|
||||
{
|
||||
Labeler = value => FormatBytes((long)value)
|
||||
Labeler = value => FormatBytes((long)value),
|
||||
LabelsPaint = new SolidColorPaint(ChartFgColor),
|
||||
TicksPaint = new SolidColorPaint(ChartFgColor),
|
||||
SeparatorsPaint = new SolidColorPaint(ChartSeparatorColor)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user