feat(03-07): create StorageView XAML, DI registration, and MainWindow wiring
- StorageView.xaml: DataGrid with IndentLevel-based name indentation - StorageView.xaml.cs: code-behind wiring DataContext to StorageViewModel - IndentConverter.cs: IndentConverter, BytesConverter, InverseBoolConverter - App.xaml: register converters and RightAlignStyle as Application.Resources - App.xaml.cs: register IStorageService, StorageCsvExportService, StorageHtmlExportService, StorageViewModel, StorageView - MainWindow.xaml: add x:Name=StorageTabItem to Storage TabItem - MainWindow.xaml.cs: wire StorageTabItem.Content from DI
This commit is contained in:
@@ -1,8 +1,15 @@
|
|||||||
<Application x:Class="SharepointToolbox.App"
|
<Application x:Class="SharepointToolbox.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:SharepointToolbox">
|
xmlns:local="clr-namespace:SharepointToolbox"
|
||||||
|
xmlns:conv="clr-namespace:SharepointToolbox.Views.Converters">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||||
|
<conv:IndentConverter x:Key="IndentConverter" />
|
||||||
|
<conv:BytesConverter x:Key="BytesConverter" />
|
||||||
|
<conv:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||||
|
<Style x:Key="RightAlignStyle" TargetType="TextBlock">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
|
</Style>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -88,6 +88,13 @@ public partial class App : Application
|
|||||||
services.AddTransient<ProfileManagementDialog>();
|
services.AddTransient<ProfileManagementDialog>();
|
||||||
services.AddTransient<SettingsView>();
|
services.AddTransient<SettingsView>();
|
||||||
|
|
||||||
|
// Phase 3: Storage
|
||||||
|
services.AddTransient<IStorageService, StorageService>();
|
||||||
|
services.AddTransient<StorageCsvExportService>();
|
||||||
|
services.AddTransient<StorageHtmlExportService>();
|
||||||
|
services.AddTransient<StorageViewModel>();
|
||||||
|
services.AddTransient<StorageView>();
|
||||||
|
|
||||||
// Phase 2: Permissions
|
// Phase 2: Permissions
|
||||||
services.AddTransient<IPermissionsService, PermissionsService>();
|
services.AddTransient<IPermissionsService, PermissionsService>();
|
||||||
services.AddTransient<ISiteListService, SiteListService>();
|
services.AddTransient<ISiteListService, SiteListService>();
|
||||||
|
|||||||
@@ -44,8 +44,8 @@
|
|||||||
<TabItem x:Name="PermissionsTabItem"
|
<TabItem x:Name="PermissionsTabItem"
|
||||||
Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.permissions]}">
|
Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.permissions]}">
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.storage]}">
|
<TabItem x:Name="StorageTabItem"
|
||||||
<controls:FeatureTabBase />
|
Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.storage]}">
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.search]}">
|
<TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.search]}">
|
||||||
<controls:FeatureTabBase />
|
<controls:FeatureTabBase />
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ public partial class MainWindow : Window
|
|||||||
// Replace Permissions tab placeholder with the DI-resolved PermissionsView
|
// Replace Permissions tab placeholder with the DI-resolved PermissionsView
|
||||||
PermissionsTabItem.Content = serviceProvider.GetRequiredService<PermissionsView>();
|
PermissionsTabItem.Content = serviceProvider.GetRequiredService<PermissionsView>();
|
||||||
|
|
||||||
|
// Replace Storage tab placeholder with the DI-resolved StorageView
|
||||||
|
StorageTabItem.Content = serviceProvider.GetRequiredService<StorageView>();
|
||||||
|
|
||||||
// Replace Settings tab placeholder with the DI-resolved SettingsView
|
// Replace Settings tab placeholder with the DI-resolved SettingsView
|
||||||
SettingsTabItem.Content = serviceProvider.GetRequiredService<SettingsView>();
|
SettingsTabItem.Content = serviceProvider.GetRequiredService<SettingsView>();
|
||||||
|
|
||||||
|
|||||||
47
SharepointToolbox/Views/Converters/IndentConverter.cs
Normal file
47
SharepointToolbox/Views/Converters/IndentConverter.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Views.Converters;
|
||||||
|
|
||||||
|
/// <summary>Converts IndentLevel (int) to WPF Thickness for DataGrid indent.</summary>
|
||||||
|
[ValueConversion(typeof(int), typeof(Thickness))]
|
||||||
|
public class IndentConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
int level = value is int i ? i : 0;
|
||||||
|
return new Thickness(level * 16, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Converts byte count (long) to human-readable size string.</summary>
|
||||||
|
[ValueConversion(typeof(long), typeof(string))]
|
||||||
|
public class BytesConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
long bytes = value is long l ? l : 0L;
|
||||||
|
if (bytes >= 1_073_741_824L) return $"{bytes / 1_073_741_824.0:F2} GB";
|
||||||
|
if (bytes >= 1_048_576L) return $"{bytes / 1_048_576.0:F2} MB";
|
||||||
|
if (bytes >= 1024L) return $"{bytes / 1024.0:F2} KB";
|
||||||
|
return $"{bytes} B";
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Inverts a bool binding — used to disable controls while an operation is running.</summary>
|
||||||
|
[ValueConversion(typeof(bool), typeof(bool))]
|
||||||
|
public class InverseBoolConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> value is bool b && !b;
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> value is bool b && !b;
|
||||||
|
}
|
||||||
104
SharepointToolbox/Views/Tabs/StorageView.xaml
Normal file
104
SharepointToolbox/Views/Tabs/StorageView.xaml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<UserControl x:Class="SharepointToolbox.Views.Tabs.StorageView"
|
||||||
|
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:conv="clr-namespace:SharepointToolbox.Views.Converters">
|
||||||
|
<DockPanel LastChildFill="True">
|
||||||
|
<!-- Options panel -->
|
||||||
|
<ScrollViewer DockPanel.Dock="Left" Width="240" VerticalScrollBarVisibility="Auto"
|
||||||
|
Margin="8,8,4,8">
|
||||||
|
<StackPanel>
|
||||||
|
<!-- Site URL -->
|
||||||
|
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[lbl.site.url]}" />
|
||||||
|
<TextBox Text="{Binding SiteUrl, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBoolConverter}}"
|
||||||
|
Height="26" Margin="0,0,0,8"
|
||||||
|
ToolTip="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[ph.site.url]}" />
|
||||||
|
|
||||||
|
<!-- Scan options group -->
|
||||||
|
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[grp.scan.opts]}"
|
||||||
|
Margin="0,0,0,8">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.per.lib]}"
|
||||||
|
IsChecked="{Binding PerLibrary}" Margin="0,2" />
|
||||||
|
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.subsites]}"
|
||||||
|
IsChecked="{Binding IncludeSubsites}" Margin="0,2" />
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||||
|
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[lbl.folder.depth]}"
|
||||||
|
VerticalAlignment="Center" Padding="0,0,4,0" />
|
||||||
|
<TextBox Text="{Binding FolderDepth, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Width="40" Height="22" VerticalAlignment="Center"
|
||||||
|
IsEnabled="{Binding IsMaxDepth, Converter={StaticResource InverseBoolConverter}}" />
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.max.depth]}"
|
||||||
|
IsChecked="{Binding IsMaxDepth}" Margin="0,2" />
|
||||||
|
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.note]}"
|
||||||
|
TextWrapping="Wrap" FontSize="11" Foreground="#888"
|
||||||
|
Margin="0,6,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.gen.storage]}"
|
||||||
|
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" />
|
||||||
|
|
||||||
|
<!-- Export group -->
|
||||||
|
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[grp.export.fmt]}"
|
||||||
|
Margin="0,0,0,8">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.rad.csv]}"
|
||||||
|
Command="{Binding ExportCsvCommand}"
|
||||||
|
Height="26" Margin="0,2" />
|
||||||
|
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.rad.html]}"
|
||||||
|
Command="{Binding ExportHtmlCommand}"
|
||||||
|
Height="26" Margin="0,2" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<TextBlock Text="{Binding StatusMessage}" TextWrapping="Wrap"
|
||||||
|
FontSize="11" Foreground="#555" Margin="0,4" />
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- Results DataGrid -->
|
||||||
|
<DataGrid x:Name="ResultsGrid"
|
||||||
|
ItemsSource="{Binding Results}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
|
VirtualizingPanel.VirtualizationMode="Recycling"
|
||||||
|
Margin="4,8,8,8">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.col.library]}"
|
||||||
|
Width="*" MinWidth="160">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Name}"
|
||||||
|
Margin="{Binding IndentLevel, Converter={StaticResource IndentConverter}}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.col.site]}"
|
||||||
|
Binding="{Binding SiteTitle}" Width="140" />
|
||||||
|
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.col.files]}"
|
||||||
|
Binding="{Binding TotalFileCount, StringFormat=N0}"
|
||||||
|
Width="70" ElementStyle="{StaticResource RightAlignStyle}" />
|
||||||
|
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.col.size]}"
|
||||||
|
Binding="{Binding TotalSizeBytes, Converter={StaticResource BytesConverter}}"
|
||||||
|
Width="100" ElementStyle="{StaticResource RightAlignStyle}" />
|
||||||
|
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.col.versions]}"
|
||||||
|
Binding="{Binding VersionSizeBytes, Converter={StaticResource BytesConverter}}"
|
||||||
|
Width="110" ElementStyle="{StaticResource RightAlignStyle}" />
|
||||||
|
<DataGridTextColumn Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[stor.col.lastmod]}"
|
||||||
|
Binding="{Binding LastModified, StringFormat=yyyy-MM-dd}"
|
||||||
|
Width="110" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</DockPanel>
|
||||||
|
</UserControl>
|
||||||
12
SharepointToolbox/Views/Tabs/StorageView.xaml.cs
Normal file
12
SharepointToolbox/Views/Tabs/StorageView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Views.Tabs;
|
||||||
|
|
||||||
|
public partial class StorageView : UserControl
|
||||||
|
{
|
||||||
|
public StorageView(ViewModels.Tabs.StorageViewModel viewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = viewModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user