Merge branch 'main' of https://git.azuze.fr/kawa/Sharepoint-Toolbox
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<Window x:Class="SharepointToolbox.Views.Dialogs.LibraryPickerDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:loc="clr-namespace:SharepointToolbox.Localization"
|
||||
Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[librarypicker.title]}"
|
||||
Width="420" Height="520" WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource AppBgBrush}"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
TextOptions.TextFormattingMode="Ideal"
|
||||
ResizeMode="CanResizeWithGrip">
|
||||
<DockPanel Margin="10">
|
||||
<TextBlock x:Name="StatusText" DockPanel.Dock="Top" Margin="0,0,0,8"
|
||||
Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[librarypicker.loading]}" />
|
||||
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[librarypicker.selectAll]}"
|
||||
Click="SelectAll_Click" Margin="0,0,6,0" Padding="6,2" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[librarypicker.selectNone]}"
|
||||
Click="SelectNone_Click" Padding="6,2" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" Margin="0,8,0,0">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.cancel]}"
|
||||
Width="80" Margin="0,0,8,0" IsCancel="True" Click="Cancel_Click" />
|
||||
<Button x:Name="OkButton"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.select]}"
|
||||
Width="80" IsDefault="True" IsEnabled="False" Click="Ok_Click" />
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl x:Name="LibrariesList">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Content="{Binding Title}" IsChecked="{Binding IsSelected, Mode=TwoWay}"
|
||||
Margin="2,4" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,105 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using Microsoft.SharePoint.Client;
|
||||
using SharepointToolbox.Services;
|
||||
|
||||
namespace SharepointToolbox.Views.Dialogs;
|
||||
|
||||
public partial class LibraryPickerDialog : Window
|
||||
{
|
||||
private readonly ClientContext _ctx;
|
||||
private readonly IVersionCleanupService _libraryLister;
|
||||
private readonly ObservableCollection<LibraryItem> _items = new();
|
||||
|
||||
public IReadOnlyList<string> SelectedLibraryTitles { get; private set; } = Array.Empty<string>();
|
||||
|
||||
public LibraryPickerDialog(
|
||||
ClientContext ctx,
|
||||
IVersionCleanupService libraryLister,
|
||||
IReadOnlyCollection<string>? preselected = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
_ctx = ctx;
|
||||
_libraryLister = libraryLister;
|
||||
LibrariesList.ItemsSource = _items;
|
||||
Loaded += async (_, _) => await LoadAsync(preselected ?? Array.Empty<string>());
|
||||
}
|
||||
|
||||
private async Task LoadAsync(IReadOnlyCollection<string> preselected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var titles = await _libraryLister.ListLibraryTitlesAsync(_ctx, CancellationToken.None);
|
||||
var preset = new HashSet<string>(preselected, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var t in titles)
|
||||
{
|
||||
var item = new LibraryItem { Title = t, IsSelected = preset.Contains(t) };
|
||||
item.PropertyChanged += OnItemChanged;
|
||||
_items.Add(item);
|
||||
}
|
||||
StatusText.Text = string.Format(
|
||||
Localization.TranslationSource.Instance["librarypicker.loaded"],
|
||||
_items.Count);
|
||||
UpdateOkEnabled();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = $"Error: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(LibraryItem.IsSelected)) UpdateOkEnabled();
|
||||
}
|
||||
|
||||
private void UpdateOkEnabled()
|
||||
=> OkButton.IsEnabled = _items.Any(i => i.IsSelected);
|
||||
|
||||
private void SelectAll_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
foreach (var i in _items) i.IsSelected = true;
|
||||
}
|
||||
|
||||
private void SelectNone_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
foreach (var i in _items) i.IsSelected = false;
|
||||
}
|
||||
|
||||
private void Ok_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SelectedLibraryTitles = _items.Where(i => i.IsSelected).Select(i => i.Title).ToList();
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
foreach (var i in _items) i.PropertyChanged -= OnItemChanged;
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
public class LibraryItem : INotifyPropertyChanged
|
||||
{
|
||||
private bool _isSelected;
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
if (_isSelected == value) return;
|
||||
_isSelected = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
|
||||
}
|
||||
}
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Profile list -->
|
||||
@@ -58,8 +59,31 @@
|
||||
Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.clientid.hint]}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Profile CRUD buttons (placed under fields for natural flow) -->
|
||||
<Grid Grid.Row="3" Margin="0,4,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.add]}"
|
||||
Command="{Binding AddCommand}"
|
||||
MinWidth="90" Padding="10,4" Margin="0,0,6,0"
|
||||
ToolTip="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.add.tooltip]}" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.save]}"
|
||||
Command="{Binding SaveCommand}"
|
||||
MinWidth="90" Padding="10,4"
|
||||
ToolTip="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.save.tooltip]}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.delete]}"
|
||||
Command="{Binding DeleteCommand}"
|
||||
MinWidth="90" Padding="10,4"
|
||||
Foreground="{DynamicResource DangerBrush}"
|
||||
ToolTip="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.delete.tooltip]}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Client Logo -->
|
||||
<StackPanel Grid.Row="3" Margin="0,8,0,8">
|
||||
<StackPanel Grid.Row="4" Margin="0,8,0,8">
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.logo.title]}" Padding="0,0,0,4" />
|
||||
<Border BorderBrush="{DynamicResource BorderSoftBrush}" BorderThickness="1" Padding="8" CornerRadius="4"
|
||||
HorizontalAlignment="Left" MinWidth="200" MinHeight="50">
|
||||
@@ -96,7 +120,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<!-- App Registration -->
|
||||
<StackPanel Grid.Row="4" Margin="0,8,0,8">
|
||||
<StackPanel Grid.Row="5" Margin="0,8,0,8">
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.fallback.title]}"
|
||||
FontWeight="SemiBold" Padding="0,0,0,4"
|
||||
Visibility="{Binding ShowFallbackInstructions, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
@@ -134,16 +158,10 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.add]}"
|
||||
Command="{Binding AddCommand}" Width="60" Margin="4,0" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.save]}"
|
||||
Command="{Binding SaveCommand}" MinWidth="80" Padding="6,0" Margin="4,0" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.delete]}"
|
||||
Command="{Binding DeleteCommand}" Width="60" Margin="4,0" />
|
||||
<!-- Close button -->
|
||||
<StackPanel Grid.Row="6" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,8,0,0">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[common.close]}"
|
||||
Width="60" Margin="4,0"
|
||||
MinWidth="80" Padding="10,4"
|
||||
Click="CloseButton_Click" IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -9,6 +9,15 @@ public partial class ProfileManagementDialog : Window
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
viewModel.ConfirmRegisterApp = msg =>
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
this, msg,
|
||||
Localization.TranslationSource.Instance["profile.register"],
|
||||
MessageBoxButton.OKCancel,
|
||||
MessageBoxImage.Information);
|
||||
return result == MessageBoxResult.OK;
|
||||
};
|
||||
Loaded += async (_, _) => await viewModel.LoadAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<UserControl x:Class="SharepointToolbox.Views.Tabs.VersionCleanupView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:loc="clr-namespace:SharepointToolbox.Localization">
|
||||
<DockPanel LastChildFill="True">
|
||||
<ScrollViewer DockPanel.Dock="Left" Width="280" VerticalScrollBarVisibility="Auto" Margin="8,8,4,8">
|
||||
<StackPanel>
|
||||
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.grp.libs]}"
|
||||
Margin="0,0,0,8">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="{Binding SelectedLibrariesLabel}" Margin="0,0,0,6"
|
||||
Foreground="{DynamicResource TextMutedBrush}" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.btn.pickLibs]}"
|
||||
Command="{Binding SelectLibrariesCommand}" Height="26" Margin="0,0,0,4" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.btn.clearLibs]}"
|
||||
Command="{Binding ClearLibrariesCommand}" Height="26" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.grp.policy]}"
|
||||
Margin="0,0,0,8">
|
||||
<StackPanel Margin="4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,2">
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.lbl.keepLast]}"
|
||||
VerticalAlignment="Center" Padding="0,0,4,0" />
|
||||
<TextBox Text="{Binding KeepLast, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="50" Height="22" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.chk.keepFirst]}"
|
||||
IsChecked="{Binding KeepFirstVersion}" Margin="0,4,0,2" />
|
||||
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.chk.confirm]}"
|
||||
IsChecked="{Binding ConfirmDelete}" Margin="0,4,0,2" />
|
||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.note]}"
|
||||
TextWrapping="Wrap" FontSize="11" Foreground="{DynamicResource TextMutedBrush}"
|
||||
Margin="0,6,0,0" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.btn.run]}"
|
||||
Command="{Binding RunCommand}" Height="28" Margin="0,0,0,4" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.cancel]}"
|
||||
Command="{Binding CancelCommand}" Height="28" Margin="0,0,0,8" />
|
||||
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.exportCsv]}"
|
||||
Command="{Binding ExportCsvCommand}" Height="28" Margin="0,0,0,4" />
|
||||
|
||||
<TextBlock Text="{Binding StatusMessage}" TextWrapping="Wrap" FontSize="11"
|
||||
Foreground="{DynamicResource TextMutedBrush}" Margin="0,6" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid Margin="4,8,8,8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Background="{DynamicResource AccentSoftBrush}" CornerRadius="4"
|
||||
Padding="12,8" Margin="0,0,0,6">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding HasResults}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,24,0">
|
||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.summary.files]}"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding TotalFilesAffected, StringFormat=N0, Mode=OneWay}" Margin="4,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,24,0">
|
||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.summary.deleted]}"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding TotalVersionsDeleted, StringFormat=N0, Mode=OneWay}" Margin="4,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.summary.freed]}"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding TotalBytesFreed, Converter={StaticResource BytesConverter}, Mode=OneWay}"
|
||||
Margin="4,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<DataGrid Grid.Row="1" ItemsSource="{Binding Results}" IsReadOnly="True" AutoGenerateColumns="False"
|
||||
VirtualizingPanel.IsVirtualizing="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.library]}"
|
||||
Binding="{Binding Library}" Width="140" />
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.file]}"
|
||||
Binding="{Binding FileName}" Width="200" />
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.before]}"
|
||||
Binding="{Binding VersionsBefore, StringFormat=N0}" Width="80"
|
||||
ElementStyle="{StaticResource RightAlignStyle}" />
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.deleted]}"
|
||||
Binding="{Binding VersionsDeleted, StringFormat=N0}" Width="80"
|
||||
ElementStyle="{StaticResource RightAlignStyle}" />
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.remaining]}"
|
||||
Binding="{Binding VersionsRemaining, StringFormat=N0}" Width="90"
|
||||
ElementStyle="{StaticResource RightAlignStyle}" />
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.freed]}"
|
||||
Binding="{Binding BytesFreed, Converter={StaticResource BytesConverter}}" Width="100"
|
||||
ElementStyle="{StaticResource RightAlignStyle}" />
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.path]}"
|
||||
Binding="{Binding FileServerRelativeUrl}" Width="*" />
|
||||
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[versions.col.error]}"
|
||||
Binding="{Binding Error}" Width="160" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SharepointToolbox.Core.Models;
|
||||
using SharepointToolbox.Services;
|
||||
using SharepointToolbox.Views.Dialogs;
|
||||
|
||||
namespace SharepointToolbox.Views.Tabs;
|
||||
|
||||
public partial class VersionCleanupView : UserControl
|
||||
{
|
||||
private readonly ViewModels.Tabs.VersionCleanupViewModel _viewModel;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IVersionCleanupService _versionService;
|
||||
|
||||
public VersionCleanupView(
|
||||
ViewModels.Tabs.VersionCleanupViewModel viewModel,
|
||||
ISessionManager sessionManager,
|
||||
IVersionCleanupService versionService)
|
||||
{
|
||||
InitializeComponent();
|
||||
_viewModel = viewModel;
|
||||
_sessionManager = sessionManager;
|
||||
_versionService = versionService;
|
||||
DataContext = viewModel;
|
||||
|
||||
viewModel.PickLibrariesAsync = async (siteUrl, preselected) =>
|
||||
{
|
||||
if (viewModel.CurrentProfile == null) return null;
|
||||
var profile = new TenantProfile
|
||||
{
|
||||
TenantUrl = siteUrl,
|
||||
ClientId = viewModel.CurrentProfile.ClientId,
|
||||
Name = viewModel.CurrentProfile.Name,
|
||||
};
|
||||
var ctx = await _sessionManager.GetOrCreateContextAsync(profile, CancellationToken.None);
|
||||
var dlg = new LibraryPickerDialog(ctx, _versionService, preselected)
|
||||
{
|
||||
Owner = Window.GetWindow(this)
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return null;
|
||||
return dlg.SelectedLibraryTitles;
|
||||
};
|
||||
|
||||
viewModel.ConfirmAction = msg =>
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
Window.GetWindow(this), msg,
|
||||
Localization.TranslationSource.Instance["versions.tab"],
|
||||
MessageBoxButton.OKCancel,
|
||||
MessageBoxImage.Warning);
|
||||
return result == MessageBoxResult.OK;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user