feat(04-07): add Phase 4 localization, shared dialogs, and example CSV resources
- Add 80+ Phase 4 EN/FR localization keys to Strings.resx and Strings.fr.resx (tabs, transfer, bulkmembers, bulksites, folderstruct, templates, bulk-shared, folderbrowser) - Add ResourceManager property accessors for all new keys to Strings.Designer.cs - Create ConfirmBulkOperationDialog (XAML + code-behind) with Proceed/Cancel buttons - Create FolderBrowserDialog (XAML + code-behind) with lazy-loading TreeView of SharePoint libraries/folders - Bundle bulk_add_members.csv, bulk_create_sites.csv, folder_structure.csv as EmbeddedResource in csproj
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
<Window x:Class="SharepointToolbox.Views.Dialogs.ConfirmBulkOperationDialog"
|
||||
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=[bulk.confirm.title]}"
|
||||
Width="450" Height="220" WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize">
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock x:Name="MessageText" Grid.Row="0"
|
||||
TextWrapping="Wrap" FontSize="14"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" Margin="0,20,0,0">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.cancel]}"
|
||||
Width="100" Margin="0,0,10,0" IsCancel="True"
|
||||
Click="Cancel_Click" />
|
||||
<Button x:Name="ProceedButton"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.proceed]}"
|
||||
Width="100" IsDefault="True"
|
||||
Click="Proceed_Click" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace SharepointToolbox.Views.Dialogs;
|
||||
|
||||
public partial class ConfirmBulkOperationDialog : Window
|
||||
{
|
||||
public bool IsConfirmed { get; private set; }
|
||||
|
||||
public ConfirmBulkOperationDialog(string message)
|
||||
{
|
||||
InitializeComponent();
|
||||
MessageText.Text = message;
|
||||
}
|
||||
|
||||
private void Proceed_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsConfirmed = true;
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsConfirmed = false;
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
28
SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml
Normal file
28
SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml
Normal file
@@ -0,0 +1,28 @@
|
||||
<Window x:Class="SharepointToolbox.Views.Dialogs.FolderBrowserDialog"
|
||||
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=[folderbrowser.title]}"
|
||||
Width="400" Height="500" WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="CanResizeWithGrip">
|
||||
<DockPanel Margin="10">
|
||||
<!-- Status -->
|
||||
<TextBlock x:Name="StatusText" DockPanel.Dock="Top" Margin="0,0,0,10"
|
||||
Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.loading]}" />
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.cancel]}"
|
||||
Width="80" Margin="0,0,10,0" IsCancel="True"
|
||||
Click="Cancel_Click" />
|
||||
<Button x:Name="SelectButton"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.select]}"
|
||||
Width="80" IsDefault="True" IsEnabled="False"
|
||||
Click="Select_Click" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Tree -->
|
||||
<TreeView x:Name="FolderTree" SelectedItemChanged="FolderTree_SelectedItemChanged" />
|
||||
</DockPanel>
|
||||
</Window>
|
||||
125
SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml.cs
Normal file
125
SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.SharePoint.Client;
|
||||
using SharepointToolbox.Core.Helpers;
|
||||
|
||||
namespace SharepointToolbox.Views.Dialogs;
|
||||
|
||||
public partial class FolderBrowserDialog : Window
|
||||
{
|
||||
private readonly ClientContext _ctx;
|
||||
public string SelectedLibrary { get; private set; } = string.Empty;
|
||||
public string SelectedFolderPath { get; private set; } = string.Empty;
|
||||
|
||||
public FolderBrowserDialog(ClientContext ctx)
|
||||
{
|
||||
InitializeComponent();
|
||||
_ctx = ctx;
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load libraries
|
||||
var web = _ctx.Web;
|
||||
var lists = _ctx.LoadQuery(web.Lists
|
||||
.Include(l => l.Title, l => l.Hidden, l => l.BaseType, l => l.RootFolder)
|
||||
.Where(l => !l.Hidden && l.BaseType == BaseType.DocumentLibrary));
|
||||
var progress = new Progress<Core.Models.OperationProgress>();
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(_ctx, progress, CancellationToken.None);
|
||||
|
||||
foreach (var list in lists)
|
||||
{
|
||||
var libNode = new TreeViewItem
|
||||
{
|
||||
Header = list.Title,
|
||||
Tag = new FolderNodeInfo(list.Title, string.Empty),
|
||||
};
|
||||
// Add dummy child for expand arrow
|
||||
libNode.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||
libNode.Expanded += LibNode_Expanded;
|
||||
FolderTree.Items.Add(libNode);
|
||||
}
|
||||
|
||||
StatusText.Text = $"{FolderTree.Items.Count} libraries loaded.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = $"Error: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async void LibNode_Expanded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not TreeViewItem node || node.Tag is not FolderNodeInfo info)
|
||||
return;
|
||||
|
||||
// Only load children once
|
||||
if (node.Items.Count == 1 && node.Items[0] is TreeViewItem dummy && dummy.Header?.ToString() == "Loading...")
|
||||
{
|
||||
node.Items.Clear();
|
||||
try
|
||||
{
|
||||
var folderUrl = string.IsNullOrEmpty(info.FolderPath)
|
||||
? GetLibraryRootUrl(info.LibraryTitle)
|
||||
: info.FolderPath;
|
||||
|
||||
var folder = _ctx.Web.GetFolderByServerRelativeUrl(folderUrl);
|
||||
_ctx.Load(folder, f => f.Folders.Include(sf => sf.Name, sf => sf.ServerRelativeUrl));
|
||||
var progress = new Progress<Core.Models.OperationProgress>();
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(_ctx, progress, CancellationToken.None);
|
||||
|
||||
foreach (var subFolder in folder.Folders)
|
||||
{
|
||||
if (subFolder.Name.StartsWith("_") || subFolder.Name == "Forms")
|
||||
continue;
|
||||
|
||||
var childNode = new TreeViewItem
|
||||
{
|
||||
Header = subFolder.Name,
|
||||
Tag = new FolderNodeInfo(info.LibraryTitle, subFolder.ServerRelativeUrl),
|
||||
};
|
||||
childNode.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||
childNode.Expanded += LibNode_Expanded;
|
||||
node.Items.Add(childNode);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
node.Items.Add(new TreeViewItem { Header = $"Error: {ex.Message}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLibraryRootUrl(string libraryTitle)
|
||||
{
|
||||
var uri = new Uri(_ctx.Url);
|
||||
return $"{uri.AbsolutePath.TrimEnd('/')}/{libraryTitle}";
|
||||
}
|
||||
|
||||
private void FolderTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
if (e.NewValue is TreeViewItem node && node.Tag is FolderNodeInfo info)
|
||||
{
|
||||
SelectedLibrary = info.LibraryTitle;
|
||||
SelectedFolderPath = info.FolderPath;
|
||||
SelectButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Select_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private record FolderNodeInfo(string LibraryTitle, string FolderPath);
|
||||
}
|
||||
Reference in New Issue
Block a user