From 5920d42614eec1f1e7d451086a6524f46f37d335 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 2 Apr 2026 12:32:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(01-06):=20build=20WPF=20shell=20=E2=80=94?= =?UTF-8?q?=20MainWindow=20XAML,=20ViewModels,=20LogPanelSink=20wiring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add FeatureTabBase UserControl with ProgressBar/TextBlock/CancelButton strip (Visibility bound to IsRunning, shown only during operations) - Add MainWindowViewModel with TenantProfiles ObservableCollection, ConnectCommand, ClearSessionCommand, ManageProfilesCommand, ProgressUpdatedMessage subscription - Add ProfileManagementViewModel wrapping ProfileService CRUD with input validation - Add SettingsViewModel (extends FeatureViewModelBase) with language/folder settings - Update MainWindow.xaml: DockPanel shell with Toolbar, TabControl (8 tabs), 150px RichTextBox LogPanel, StatusBar (tenant name | ProgressStatus | ProgressPercentage) - MainWindow.xaml.cs: DI constructor, DataContext=viewModel, LoadProfilesAsync on Loaded - App.xaml.cs: register all services, wire LogPanelSink after MainWindow resolved, register DispatcherUnhandledException and UnobservedTaskException global handlers - App.xaml: add BoolToVisibilityConverter resource --- SharepointToolbox/App.xaml | 2 +- SharepointToolbox/App.xaml.cs | 53 ++++++-- SharepointToolbox/MainWindow.xaml | 69 +++++++++- SharepointToolbox/MainWindow.xaml.cs | 29 ++-- .../ViewModels/MainWindowViewModel.cs | 127 ++++++++++++++++++ .../ViewModels/ProfileManagementViewModel.cs | 125 +++++++++++++++++ .../ViewModels/Tabs/SettingsViewModel.cs | 94 +++++++++++++ .../Views/Controls/FeatureTabBase.xaml | 33 +++++ .../Views/Controls/FeatureTabBase.xaml.cs | 11 ++ 9 files changed, 516 insertions(+), 27 deletions(-) create mode 100644 SharepointToolbox/ViewModels/MainWindowViewModel.cs create mode 100644 SharepointToolbox/ViewModels/ProfileManagementViewModel.cs create mode 100644 SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs create mode 100644 SharepointToolbox/Views/Controls/FeatureTabBase.xaml create mode 100644 SharepointToolbox/Views/Controls/FeatureTabBase.xaml.cs diff --git a/SharepointToolbox/App.xaml b/SharepointToolbox/App.xaml index 1cfc57a..b937526 100644 --- a/SharepointToolbox/App.xaml +++ b/SharepointToolbox/App.xaml @@ -3,6 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SharepointToolbox"> - + diff --git a/SharepointToolbox/App.xaml.cs b/SharepointToolbox/App.xaml.cs index 3bfe562..0746383 100644 --- a/SharepointToolbox/App.xaml.cs +++ b/SharepointToolbox/App.xaml.cs @@ -1,13 +1,17 @@ +using System.IO; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Serilog; +using SharepointToolbox.Infrastructure.Auth; +using SharepointToolbox.Infrastructure.Logging; +using SharepointToolbox.Services; +using SharepointToolbox.ViewModels; +using SharepointToolbox.ViewModels.Tabs; using System.Windows; namespace SharepointToolbox; -/// -/// Interaction logic for App.xaml -/// public partial class App : Application { [STAThread] @@ -16,7 +20,7 @@ public partial class App : Application using IHost host = Host.CreateDefaultBuilder(args) .UseSerilog((ctx, cfg) => cfg .WriteTo.File( - System.IO.Path.Combine( + Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SharepointToolbox", "logs", "app-.log"), rollingInterval: RollingInterval.Day, @@ -27,16 +31,49 @@ public partial class App : Application host.Start(); App app = new(); app.InitializeComponent(); - app.MainWindow = host.Services.GetRequiredService(); + + var mainWindow = host.Services.GetRequiredService(); + + // Wire LogPanelSink now that we have the RichTextBox + Log.Logger = new LoggerConfiguration() + .WriteTo.File( + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "SharepointToolbox", "logs", "app-.log"), + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 30) + .WriteTo.Sink(new LogPanelSink(mainWindow.GetLogPanel())) + .CreateLogger(); + + // Global exception handlers + app.DispatcherUnhandledException += (s, e) => + { + Log.Fatal(e.Exception, "Unhandled UI exception"); + MessageBox.Show( + $"A fatal error occurred:\n{e.Exception.Message}\n\nCheck log for details.", + "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error); + e.Handled = true; + }; + TaskScheduler.UnobservedTaskException += (s, e) => + { + Log.Fatal(e.Exception, "Unobserved task exception"); + e.SetObserved(); + }; + + app.MainWindow = mainWindow; app.MainWindow.Visibility = Visibility.Visible; app.Run(); } private static void RegisterServices(HostBuilderContext ctx, IServiceCollection services) { - // Placeholder — services registered in subsequent plans + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); services.AddSingleton(); - // LogPanelSink registered in plan 01-06 after MainWindow is created - // (requires RichTextBox reference from MainWindow) } } diff --git a/SharepointToolbox/MainWindow.xaml b/SharepointToolbox/MainWindow.xaml index 2dbf194..934d97c 100644 --- a/SharepointToolbox/MainWindow.xaml +++ b/SharepointToolbox/MainWindow.xaml @@ -1,12 +1,71 @@ - - + Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[app.title]}" + MinWidth="900" MinHeight="600" Height="700" Width="1100"> + + + + +