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:
Dev
2026-04-20 11:23:11 +02:00
parent 8f30a60d2a
commit 12dd1de9f2
93 changed files with 8708 additions and 1159 deletions
@@ -5,4 +5,5 @@ public class AppSettings
public string DataFolder { get; set; } = string.Empty;
public string Lang { get; set; } = "en";
public bool AutoTakeOwnership { get; set; } = false;
public string Theme { get; set; } = "System"; // System | Light | Dark
}
@@ -10,4 +10,8 @@ public class DuplicateItem
public DateTime? Modified { get; set; }
public int? FolderCount { get; set; }
public int? FileCount { get; set; }
/// <summary>URL of the site the item was collected from.</summary>
public string SiteUrl { get; set; } = string.Empty;
/// <summary>Friendly site title; falls back to a derived label when unknown.</summary>
public string SiteTitle { get; set; } = string.Empty;
}
@@ -1,7 +1,7 @@
namespace SharepointToolbox.Core.Models;
public record OperationProgress(int Current, int Total, string Message)
public record OperationProgress(int Current, int Total, string Message, bool IsIndeterminate = false)
{
public static OperationProgress Indeterminate(string message) =>
new(0, 0, message);
new(0, 0, message, IsIndeterminate: true);
}
+40 -1
View File
@@ -1,3 +1,42 @@
namespace SharepointToolbox.Core.Models;
public record SiteInfo(string Url, string Title);
public record SiteInfo(string Url, string Title)
{
public long StorageUsedMb { get; init; }
public long StorageQuotaMb { get; init; }
public string Template { get; init; } = string.Empty;
public SiteKind Kind => SiteKindHelper.FromTemplate(Template);
}
public enum SiteKind
{
Unknown,
TeamSite,
CommunicationSite,
Classic
}
public static class SiteKindHelper
{
public static SiteKind FromTemplate(string template)
{
if (string.IsNullOrEmpty(template)) return SiteKind.Unknown;
if (template.StartsWith("GROUP#", StringComparison.OrdinalIgnoreCase)) return SiteKind.TeamSite;
if (template.StartsWith("SITEPAGEPUBLISHING#", StringComparison.OrdinalIgnoreCase)) return SiteKind.CommunicationSite;
if (template.StartsWith("STS#", StringComparison.OrdinalIgnoreCase)) return SiteKind.Classic;
return SiteKind.Unknown;
}
public static string DisplayName(SiteKind kind)
{
var key = kind switch
{
SiteKind.TeamSite => "sitepicker.kind.teamsite",
SiteKind.CommunicationSite => "sitepicker.kind.communication",
SiteKind.Classic => "sitepicker.kind.classic",
_ => "sitepicker.kind.other"
};
return Localization.TranslationSource.Instance[key];
}
}
@@ -10,4 +10,24 @@ public class TransferJob
public string DestinationFolderPath { get; set; } = string.Empty;
public TransferMode Mode { get; set; } = TransferMode.Copy;
public ConflictPolicy ConflictPolicy { get; set; } = ConflictPolicy.Skip;
/// <summary>
/// Optional library-relative file paths. When non-empty, only these files
/// are transferred; SourceFolderPath recursive enumeration is skipped.
/// </summary>
public IReadOnlyList<string> SelectedFilePaths { get; set; } = Array.Empty<string>();
/// <summary>
/// When true, recreate the source folder name under the destination folder
/// (dest/srcFolderName/... ). When false, the source folder's contents land
/// directly inside the destination folder.
/// </summary>
public bool IncludeSourceFolder { get; set; }
/// <summary>
/// When true (default), transfer the files inside the source folder.
/// When false, only create the folder structure (useful together with
/// <see cref="IncludeSourceFolder"/> to clone an empty scaffold).
/// </summary>
public bool CopyFolderContents { get; set; } = true;
}