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
@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:SharepointToolbox.Localization"
xmlns:common="clr-namespace:SharepointToolbox.Views.Common"
xmlns:models="clr-namespace:SharepointToolbox.Core.Models"
xmlns:converters="clr-namespace:SharepointToolbox.Core.Converters">
@@ -21,7 +22,9 @@
</Grid.RowDefinitions>
<!-- Left panel: Scan configuration -->
<DockPanel Grid.Column="0" Grid.Row="0" Margin="8">
<ScrollViewer Grid.Column="0" Grid.Row="0"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<DockPanel Margin="8">
<!-- Scan Options GroupBox -->
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[grp.scan.opts]}"
@@ -65,7 +68,7 @@
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSimplifiedMode}" Value="False">
<Setter Property="Foreground" Value="Gray" />
<Setter Property="Foreground" Value="{DynamicResource TextMutedBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
@@ -108,6 +111,16 @@
Command="{Binding CancelCommand}"
Margin="0,0,0,4" Padding="6,3" />
</Grid>
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[export.split.label]}" Padding="0,2" />
<ComboBox SelectedIndex="{Binding SplitModeIndex}" Height="24" Margin="0,0,0,4">
<ComboBoxItem Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[export.split.single]}" />
<ComboBoxItem Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[export.split.bySite]}" />
</ComboBox>
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[export.html.layout.label]}" Padding="0,2" />
<ComboBox SelectedIndex="{Binding HtmlLayoutIndex}" Height="24" Margin="0,0,0,6">
<ComboBoxItem Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[export.html.layout.separate]}" />
<ComboBoxItem Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[export.html.layout.tabbed]}" />
</ComboBox>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -125,6 +138,7 @@
</StackPanel>
</DockPanel>
</ScrollViewer>
<!-- Right panel: Summary + Results -->
<Grid Grid.Column="1" Grid.Row="0" Margin="0,8,8,8">
@@ -153,7 +167,8 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border CornerRadius="6" Padding="14,10" Margin="0,0,10,4" MinWidth="140">
<Border CornerRadius="6" Padding="14,10" Margin="0,0,10,4" MinWidth="140"
TextElement.Foreground="{DynamicResource OnColoredBgBrush}">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#F3F4F6" />
@@ -182,7 +197,7 @@
</Style>
</Border.Style>
<StackPanel>
<TextBlock Text="{Binding Count}" FontSize="22" FontWeight="Bold" />
<TextBlock Text="{Binding Count}" FontSize="22" FontWeight="Bold" Foreground="#1F2430" />
<TextBlock Text="{Binding Label}" FontSize="11" Foreground="#555" />
<TextBlock FontSize="10" Foreground="#888" Margin="0,2,0,0">
<Run Text="{Binding DistinctUsers, Mode=OneWay}" />
@@ -222,19 +237,24 @@
<Style.Triggers>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.High}">
<Setter Property="Background" Value="#FEF2F2" />
<Setter Property="Foreground" Value="#1F2430" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.Medium}">
<Setter Property="Background" Value="#FFFBEB" />
<Setter Property="Foreground" Value="#1F2430" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.Low}">
<Setter Property="Background" Value="#ECFDF5" />
<Setter Property="Foreground" Value="#1F2430" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.ReadOnly}">
<Setter Property="Background" Value="#EFF6FF" />
<Setter Property="Foreground" Value="#1F2430" />
</DataTrigger>
<!-- Phase 18: auto-elevated rows get amber background + tooltip -->
<DataTrigger Binding="{Binding WasAutoElevated}" Value="True">
<Setter Property="Background" Value="#FFF9E6" />
<Setter Property="Foreground" Value="#1F2430" />
<Setter Property="ToolTip" Value="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[permissions.elevated.tooltip]}" />
</DataTrigger>
</Style.Triggers>
@@ -261,15 +281,22 @@
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Object Type" Binding="{Binding ObjectType}" Width="100" />
<DataGridTextColumn Header="Title" Binding="{Binding Title}" Width="140" />
<DataGridTextColumn Header="URL" Binding="{Binding Url}" Width="200" />
<DataGridTextColumn Header="Unique Perms" Binding="{Binding HasUniquePermissions}" Width="90" />
<DataGridTextColumn Header="Users" Binding="{Binding Users}" Width="140" />
<DataGridTextColumn Header="Permission Levels" Binding="{Binding PermissionLevels}" Width="140" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[report.col.object_type]}"
Binding="{Binding ObjectType}" Width="100" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[report.col.title]}"
Binding="{Binding Title}" Width="140" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[report.col.url]}"
Binding="{Binding Url}" Width="200" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[perm.col.unique_perms]}"
Binding="{Binding HasUniquePermissions}" Width="90" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[report.col.users_groups]}"
Binding="{Binding Users}" Width="140" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[perm.col.permission_levels]}"
Binding="{Binding PermissionLevels}" Width="140" />
<!-- Simplified Labels column (only visible in simplified mode) -->
<DataGridTextColumn Header="Simplified" Binding="{Binding SimplifiedLabels}" Width="200">
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[report.col.simplified]}"
Binding="{Binding SimplifiedLabels}" Width="200">
<DataGridTextColumn.Visibility>
<Binding Path="DataContext.IsSimplifiedMode"
RelativeSource="{RelativeSource AncestorType=DataGrid}"
@@ -277,8 +304,10 @@
</DataGridTextColumn.Visibility>
</DataGridTextColumn>
<DataGridTextColumn Header="Granted Through" Binding="{Binding GrantedThrough}" Width="140" />
<DataGridTextColumn Header="Principal Type" Binding="{Binding PrincipalType}" Width="110" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[report.col.granted_through]}"
Binding="{Binding GrantedThrough}" Width="140" />
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[perm.col.principal_type]}"
Binding="{Binding PrincipalType}" Width="110" />
</DataGrid.Columns>
</DataGrid>
</Grid>
@@ -286,9 +315,8 @@
<!-- Bottom: status bar spanning both columns -->
<StatusBar Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1">
<StatusBarItem>
<ProgressBar Width="150" Height="14"
Value="{Binding ProgressValue}"
Minimum="0" Maximum="100" />
<common:Spinner Width="14" Height="14"
Visibility="{Binding IsRunning, Converter={StaticResource BoolToVisibilityConverter}}" />
</StatusBarItem>
<StatusBarItem Content="{Binding StatusMessage}" />
</StatusBar>