diff --git a/.claude/settings.local.json b/.claude/settings.local.json index cb4905b..0ee5e76 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(find:*)", - "Bash(ls:*)" + "Bash(ls:*)", + "Bash(dotnet sln:*)" ] } } diff --git a/.gitignore b/.gitignore index ed1df53..1678189 100644 --- a/.gitignore +++ b/.gitignore @@ -483,4 +483,5 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp -*.exe \ No newline at end of file +*.exe +/Data diff --git a/VisionBuilder.UI.Blazor.Uno/Components/App.razor b/VisionBuilder.UI.Blazor.Uno/Components/App.razor new file mode 100644 index 0000000..e3a9d3d --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/Components/App.razor @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/VisionBuilder.UI.Blazor.Uno/Components/Layout/MainLayout.razor b/VisionBuilder.UI.Blazor.Uno/Components/Layout/MainLayout.razor new file mode 100644 index 0000000..edcf3a9 --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/Components/Layout/MainLayout.razor @@ -0,0 +1,30 @@ +@inherits LayoutComponentBase +@using MudBlazor +@using VisionBuilder.UI.Common.ViewModel +@using VisionBuilder.UI.Common.Services +@using VisionBuilder.UI.Common +@inject MainWindowVM MainWindowVm +@inject IPasswordInputService PasswordInputService +@implements IDisposable + + + + + + +
+
+ @Body +
+
+
+
+ @if (_showTestModeButton) + { + Test Mode + } + Settings +
+ @_version +
+
diff --git a/VisionBuilder.UI.Blazor.Uno/Components/Layout/MainLayout.razor.cs b/VisionBuilder.UI.Blazor.Uno/Components/Layout/MainLayout.razor.cs new file mode 100644 index 0000000..24f0c3d --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/Components/Layout/MainLayout.razor.cs @@ -0,0 +1,103 @@ +using System.ComponentModel; +using System.Reflection; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using VisionBuilder.UI.Blazor.Services; +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.Services; +using VisionBuilder.UI.Common.ViewModel; +using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; +using Ninject; + +namespace VisionBuilder.UI.Blazor.Uno.Components.Layout; + +public partial class MainLayout : IDisposable +{ + [Microsoft.AspNetCore.Components.Inject] private IDialogService DialogService { get; set; } = null!; + [Microsoft.AspNetCore.Components.Inject] private IKernel Kernel { get; set; } = null!; + + private bool _testMode; + private bool _showTestModeButton; + private string _version = ""; + + private readonly MudTheme _inspectronTheme = new() + { + PaletteLight = new PaletteLight + { + Primary = "#d32f2f", + AppbarBackground = "#37474f" + }, + LayoutProperties = new LayoutProperties + { + DefaultBorderRadius = "0px" + } + }; + + private readonly MudTheme _testModeTheme = new() + { + PaletteLight = new PaletteLight + { + Primary = "#2e7d32", + AppbarBackground = "#2e7d32" + }, + LayoutProperties = new LayoutProperties + { + DefaultBorderRadius = "0px" + } + }; + + private MudTheme _currentTheme = null!; + + protected override void OnInitialized() + { + _currentTheme = _inspectronTheme; + _version = $"v{Assembly.GetEntryAssembly()?.GetName().Version?.ToString(3) ?? "0.0.0"}"; + + var uiConfig = Kernel.Get(); + _showTestModeButton = uiConfig.ShowTestModeButton; + + MainWindowVm.PropertyChanged += OnPropertyChanged; + + // Wire up dialog services + if (Kernel.Get() is BlazorRecipeSelectionDialogService recipeService) + recipeService.SetDialogService(DialogService); + if (Kernel.Get() is BlazorImagePreviewService imageService) + imageService.SetDialogService(DialogService); + if (Kernel.Get() is BlazorPasswordInputService passwordService) + passwordService.SetDialogService(DialogService); + if (Kernel.Get() is BlazorSettingsService settingsService) + settingsService.SetDialogService(DialogService); + } + + private async void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(MainWindowVM.TestMode)) + { + _testMode = MainWindowVm.TestMode; + _currentTheme = _testMode ? _testModeTheme : _inspectronTheme; + await InvokeAsync(StateHasChanged); + } + } + + private void ToggleTestMode() + { + MainWindowVm.ToggleTestModeCommand.Execute(null); + } + + private async Task OnSettings() + { + var uiConfig = Kernel.Get(); + if (uiConfig.ProtectSettingsWithPassword) + { + var password = await PasswordInputService.GetPasswordAsync(); + if (password != uiConfig.AdminPassword) + return; + } + MainWindowVm.SettingsCommand.Execute(null); + } + + public void Dispose() + { + MainWindowVm.PropertyChanged -= OnPropertyChanged; + } +} diff --git a/VisionBuilder.UI.Blazor.Uno/Components/Routes.razor b/VisionBuilder.UI.Blazor.Uno/Components/Routes.razor new file mode 100644 index 0000000..f83f16f --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/Components/Routes.razor @@ -0,0 +1,7 @@ + + + + + + diff --git a/VisionBuilder.UI.Blazor.Uno/Pages/Home.razor b/VisionBuilder.UI.Blazor.Uno/Pages/Home.razor new file mode 100644 index 0000000..4e3811e --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/Pages/Home.razor @@ -0,0 +1,9 @@ +@page "/" +@using VisionBuilder.UI.Blazor.Components +@using VisionBuilder.UI.Common +@using VisionBuilder.UI.Common.ViewModel +@inject MainWindowVM MainWindowVm + +VisionBuilder + + diff --git a/VisionBuilder.UI.Blazor.Uno/Program.cs b/VisionBuilder.UI.Blazor.Uno/Program.cs new file mode 100644 index 0000000..174612f --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/Program.cs @@ -0,0 +1,104 @@ +using Inspectron.Settings; +using Lindt.Colorballs.Duo.Utils; +using MudBlazor.Services; +using Ninject; +using Ninject.Extensions.ChildKernel; +using VisionBuilder.UI.Blazor; +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.Plugins; +using VisionBuilder.UI.Common.Processing; +using VisionBuilder.UI.Common.ViewModel; +using VisionBuilder.UI.Console; +using VisionBuilder.UI.IOCommander; +using VisionBuilder.UI.Recipes.HawkeyeRecipe; +using VisionBuilder.UI.Ringbuffer; +using VisionBuilder.UI.Statistics; + +const string CAMERA1 = "Camera 1"; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); +builder.Services.AddMudServices(); + +// Parse command line args +CommandLineParser commandLineParser = new CommandLineParser(args); + +// Setup settings +InspectronSettings settings = new InspectronSettings("..\\Data\\Config"); + +var mainKernel = VisionBuilder.UI.Common.VisionBuilder.CreateMainKernel(settings); + +var profile = commandLineParser.GetStringArgument("profile", 'p'); +mainKernel.UsePlugins(profile); + +mainKernel + .UseConsole() + .UseIOCommander([CAMERA1]) + .UseBlazorServices() + .RegisterGlobalPlugins(); + +mainKernel.Get().StartLoading("Camera initialization"); + +// CAMERA 1 // +ChildKernel kernelCamera1 = new ChildKernel(mainKernel); + +kernelCamera1 + .RegisterCamera(CAMERA1) + .UseStatistics(CAMERA1) + .UseRingbuffer(CAMERA1) + .UseHawkeyeRecipes(CAMERA1) + .UseCameraIOCommander(CAMERA1) + .RegisterCameraPlugins(CAMERA1); + +// LOAD SETTINGS // +mainKernel.RegisterSettings(); +kernelCamera1.RegisterSettings(); +settings.LoadSettings(); + +// Create camera and bind it (must be after settings are loaded) +kernelCamera1.UseCamera(); + +// Initialize modules +mainKernel.InitializeModules(); +kernelCamera1.InitializeModules(); + +mainKernel.Get().StopLoading("Camera initialization"); + +var mainWindowVm = mainKernel.Get(); +mainWindowVm.SingleCameraVms = +[ + kernelCamera1.Get() +]; + +settings.LoadSettings(); + +// Bridge Ninject → ASP.NET Core DI +builder.Services.AddSingleton(mainKernel); +builder.Services.AddSingleton(mainWindowVm); +builder.Services.AddSingleton(kernelCamera1.Get()); +builder.Services.AddSingleton(mainKernel.Get()); + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); +} + +app.UseStaticFiles(); +app.UseAntiforgery(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddAdditionalAssemblies(typeof(VisionBuilder.UI.Blazor.Components.SingleCameraControl).Assembly); + +mainWindowVm.OnProgramStarted(); + +app.Lifetime.ApplicationStopping.Register(() => +{ + mainWindowVm.OnProgramClosed(); +}); + +app.Run(); diff --git a/VisionBuilder.UI.Blazor.Uno/Properties/launchSettings.json b/VisionBuilder.UI.Blazor.Uno/Properties/launchSettings.json new file mode 100644 index 0000000..04ceaf6 --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "VisionBuilder.UI.Blazor.Uno": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/VisionBuilder.UI.Blazor.Uno/VisionBuilder.UI.Blazor.Uno.csproj b/VisionBuilder.UI.Blazor.Uno/VisionBuilder.UI.Blazor.Uno.csproj new file mode 100644 index 0000000..2853896 --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/VisionBuilder.UI.Blazor.Uno.csproj @@ -0,0 +1,35 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/VisionBuilder.UI.Blazor.Uno/_Imports.razor b/VisionBuilder.UI.Blazor.Uno/_Imports.razor new file mode 100644 index 0000000..69e01aa --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using MudBlazor +@using VisionBuilder.UI.Blazor.Uno +@using VisionBuilder.UI.Blazor.Uno.Components diff --git a/VisionBuilder.UI.Blazor.Uno/appsettings.Development.json b/VisionBuilder.UI.Blazor.Uno/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/VisionBuilder.UI.Blazor.Uno/appsettings.json b/VisionBuilder.UI.Blazor.Uno/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/VisionBuilder.UI.Blazor.Uno/wwwroot/css/app.css b/VisionBuilder.UI.Blazor.Uno/wwwroot/css/app.css new file mode 100644 index 0000000..9c1c4cc --- /dev/null +++ b/VisionBuilder.UI.Blazor.Uno/wwwroot/css/app.css @@ -0,0 +1,65 @@ +html, body { + margin: 0; + padding: 0; + font-family: 'Roboto', sans-serif; + height: 100%; + overflow: hidden; +} + +/* Main app layout */ +.app-root { + display: flex; + flex-direction: column; + height: 100vh; + background: #fff; +} + +.app-content { + flex: 1; + min-height: 0; + overflow: hidden; +} + +.app-divider { + height: 1px; + background-color: #37474f; + flex-shrink: 0; +} + +.app-toolbar { + display: flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + flex-shrink: 0; + position: relative; + min-height: 56px; +} + +.toolbar-buttons { + display: flex; + gap: 8px; +} + +.toolbar-btn { + min-width: 140px !important; + height: 48px !important; + font-weight: 600 !important; + font-size: 0.85rem !important; +} + +.toolbar-version { + position: absolute; + right: 16px; + font-size: 0.75rem; + color: #666; +} + +/* Test mode: green toolbar tint */ +.app-root.test-mode .app-toolbar { + background-color: #e8f5e9; +} + +.app-root.test-mode .app-divider { + background-color: #2e7d32; +} diff --git a/VisionBuilder.UI.Blazor/BlazorUISettings.cs b/VisionBuilder.UI.Blazor/BlazorUISettings.cs new file mode 100644 index 0000000..cbcd584 --- /dev/null +++ b/VisionBuilder.UI.Blazor/BlazorUISettings.cs @@ -0,0 +1,14 @@ +using Inspectron.Settings; +using VisionBuilder.UI.Common; + +namespace VisionBuilder.UI.Blazor; + +public class BlazorUISettings : ISettings +{ + public bool FullScreen { get; set; } = false; + + public void RegisterSettings(InspectronSettings settings) + { + settings.RegisterSimple(this, () => this.FullScreen, "System", nameof(FullScreen)); + } +} diff --git a/VisionBuilder.UI.Blazor/Components/ErrorPreview.razor b/VisionBuilder.UI.Blazor/Components/ErrorPreview.razor new file mode 100644 index 0000000..1790481 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/ErrorPreview.razor @@ -0,0 +1,17 @@ +@implements IDisposable + +
+ @foreach (var error in _errors) + { +
+ @{ + var uri = error.ImageAnalysis?.ToBase64DataUri(50); + } + @if (!string.IsNullOrEmpty(uri)) + { + Error + } + @error.Title +
+ } +
diff --git a/VisionBuilder.UI.Blazor/Components/ErrorPreview.razor.cs b/VisionBuilder.UI.Blazor/Components/ErrorPreview.razor.cs new file mode 100644 index 0000000..10f521e --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/ErrorPreview.razor.cs @@ -0,0 +1,59 @@ +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using Microsoft.AspNetCore.Components; +using VisionBuilder.UI.Common.ViewModel; +using VisionBuilder.UI.Common.ViewModel.Classes; + +namespace VisionBuilder.UI.Blazor.Components; + +public partial class ErrorPreview : ComponentBase, IDisposable +{ + [Parameter] public ErrorsVM? ViewModel { get; set; } + + private List _errors = new(); + private ErrorsVM? _subscribedVm; + + protected override void OnParametersSet() + { + if (_subscribedVm != ViewModel) + { + Unsubscribe(); + Subscribe(); + } + } + + private void Subscribe() + { + _subscribedVm = ViewModel; + if (ViewModel != null) + { + ViewModel.Errors.CollectionChanged += OnCollectionChanged; + _errors = new List(ViewModel.Errors); + } + } + + private void Unsubscribe() + { + if (_subscribedVm != null) + _subscribedVm.Errors.CollectionChanged -= OnCollectionChanged; + _subscribedVm = null; + } + + private async void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (ViewModel != null) + _errors = new List(ViewModel.Errors); + await InvokeAsync(StateHasChanged); + } + + private async Task OnErrorClicked(ErrorData errorData) + { + if (ViewModel != null) + await ViewModel.ShowImage(errorData); + } + + public void Dispose() + { + Unsubscribe(); + } +} diff --git a/VisionBuilder.UI.Blazor/Components/PreviewWindow.razor b/VisionBuilder.UI.Blazor/Components/PreviewWindow.razor new file mode 100644 index 0000000..938f0cf --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/PreviewWindow.razor @@ -0,0 +1,12 @@ +@implements IDisposable + +
+ @if (!string.IsNullOrEmpty(_dataUri)) + { + Preview + } + else + { +
No image
+ } +
diff --git a/VisionBuilder.UI.Blazor/Components/PreviewWindow.razor.cs b/VisionBuilder.UI.Blazor/Components/PreviewWindow.razor.cs new file mode 100644 index 0000000..958ce22 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/PreviewWindow.razor.cs @@ -0,0 +1,94 @@ +using System.ComponentModel; +using Microsoft.AspNetCore.Components; +using OpenCvSharp; +using VisionBuilder.UI.Blazor.Helpers; +using VisionBuilder.UI.Common.ViewModel; + +namespace VisionBuilder.UI.Blazor.Components; + +public partial class PreviewWindow : ComponentBase, IDisposable +{ + [Parameter] public PreviewVM? ViewModel { get; set; } + + private string _dataUri = string.Empty; + private bool _isError; + private DateTime _lastRenderTime = DateTime.MinValue; + private Timer? _pendingTimer; + private PreviewVM? _subscribedVm; + + protected override void OnParametersSet() + { + if (_subscribedVm != ViewModel) + { + Unsubscribe(); + Subscribe(); + } + } + + private void Subscribe() + { + _subscribedVm = ViewModel; + if (ViewModel != null) + ViewModel.PropertyChanged += OnPropertyChanged; + } + + private void Unsubscribe() + { + if (_subscribedVm != null) + _subscribedVm.PropertyChanged -= OnPropertyChanged; + _subscribedVm = null; + } + + private async void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != nameof(PreviewVM.ImagePreview) && e.PropertyName != nameof(PreviewVM.IsError)) + return; + + var now = DateTime.UtcNow; + var elapsed = (now - _lastRenderTime).TotalMilliseconds; + + if (elapsed < 100) + { + // Schedule a render for when the throttle window expires, + // so we always show the latest frame. + _pendingTimer?.Dispose(); + _pendingTimer = new Timer(_ => RenderLatest(), null, + (int)(100 - elapsed) + 1, Timeout.Infinite); + return; + } + + await RenderCurrentFrame(); + } + + private async void RenderLatest() + { + _pendingTimer?.Dispose(); + _pendingTimer = null; + await RenderCurrentFrame(); + } + + private async Task RenderCurrentFrame() + { + _lastRenderTime = DateTime.UtcNow; + + var mat = ViewModel?.ImagePreview; + var isError = ViewModel?.IsError ?? false; + + string dataUri = string.Empty; + if (mat != null && !mat.Empty()) + { + dataUri = mat.ToBase64DataUri(); + } + + _dataUri = dataUri; + _isError = isError; + + await InvokeAsync(StateHasChanged); + } + + public void Dispose() + { + _pendingTimer?.Dispose(); + Unsubscribe(); + } +} diff --git a/VisionBuilder.UI.Blazor/Components/SingleCameraControl.razor b/VisionBuilder.UI.Blazor/Components/SingleCameraControl.razor new file mode 100644 index 0000000..8c34ebd --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/SingleCameraControl.razor @@ -0,0 +1,75 @@ +@using VisionBuilder.UI.Common +@implements IDisposable + +
+
+
+ @_cameraLabel +
+
+ Errors +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + @_currentRecipeName + + + Start + + + Stop + + @if (_showPauseButton && _canPause) + { + + Pause + + } + @if (_showPauseButton && _canResume) + { + + Resume + + } + @if (_canHotReload) + { + + Hot Reload + + } +
+
+
+
diff --git a/VisionBuilder.UI.Blazor/Components/SingleCameraControl.razor.cs b/VisionBuilder.UI.Blazor/Components/SingleCameraControl.razor.cs new file mode 100644 index 0000000..0ae3243 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/SingleCameraControl.razor.cs @@ -0,0 +1,108 @@ +using System.ComponentModel; +using Microsoft.AspNetCore.Components; +using VisionBuilder.UI.Common; + +namespace VisionBuilder.UI.Blazor.Components; + +public partial class SingleCameraControl : ComponentBase, IDisposable +{ + [Parameter] public SingleCameraVM? ViewModel { get; set; } + + private string _cameraLabel = ""; + private string _currentRecipeName = "Select recipe"; + private bool _canSelectRecipe; + private bool _canStart; + private bool _canStop; + private bool _canPause; + private bool _canResume; + private bool _canHotReload; + private bool _showPauseButton; + private SingleCameraVM? _subscribedVm; + + protected override void OnParametersSet() + { + if (_subscribedVm != ViewModel) + { + Unsubscribe(); + Subscribe(); + } + } + + private void Subscribe() + { + _subscribedVm = ViewModel; + if (ViewModel != null) + { + ViewModel.PropertyChanged += OnPropertyChanged; + UpdateState(); + } + } + + private void Unsubscribe() + { + if (_subscribedVm != null) + _subscribedVm.PropertyChanged -= OnPropertyChanged; + _subscribedVm = null; + } + + private void UpdateState() + { + if (ViewModel == null) return; + _cameraLabel = ViewModel.CameraLabelAndStatus; + _currentRecipeName = ViewModel.CurrentRecipeName; + _canSelectRecipe = ViewModel.IsNotRunning; + _canStart = ViewModel.CanStart; + _canStop = ViewModel.CanStop; + _canPause = ViewModel.CanPause; + _canResume = ViewModel.CanResume; + _canHotReload = ViewModel.CanHotReload; + _showPauseButton = ViewModel.CanPause || ViewModel.CanResume; + } + + private async void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + UpdateState(); + await InvokeAsync(StateHasChanged); + } + + private async Task OnSelectRecipe() + { + if (ViewModel?.SelectRecipeCommand.CanExecute(null) == true) + await ViewModel.SelectRecipe(); + } + + private async Task OnStart() + { + if (ViewModel?.StartCommand.CanExecute(null) == true) + await ViewModel.Start(); + } + + private async Task OnStop() + { + if (ViewModel?.StopCommand.CanExecute(null) == true) + await ViewModel.Stop(); + } + + private void OnPause() + { + if (ViewModel?.PauseCommand.CanExecute(null) == true) + ViewModel.PauseCommand.Execute(null); + } + + private void OnResume() + { + if (ViewModel?.ResumeCommand.CanExecute(null) == true) + ViewModel.ResumeCommand.Execute(null); + } + + private void OnHotReload() + { + if (ViewModel?.HotReloadCommand.CanExecute(null) == true) + ViewModel.HotReloadCommand.Execute(null); + } + + public void Dispose() + { + Unsubscribe(); + } +} diff --git a/VisionBuilder.UI.Blazor/Components/Stats.razor b/VisionBuilder.UI.Blazor/Components/Stats.razor new file mode 100644 index 0000000..0873fdd --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/Stats.razor @@ -0,0 +1,34 @@ +@implements IDisposable + +
+ @if (!string.IsNullOrEmpty(_sessionStarted)) + { +
+ Started at: + @_sessionStarted +
+
+ Processing Time: + @_processingTime +
+
+ Total: + @_total +
+
+ Bad: + @_bad +
+
+ Error rate: + @_errorRate +
+ @if (!string.IsNullOrEmpty(_statisticsDetails)) + { +
+ Details: +
@_statisticsDetails
+
+ } + } +
diff --git a/VisionBuilder.UI.Blazor/Components/Stats.razor.cs b/VisionBuilder.UI.Blazor/Components/Stats.razor.cs new file mode 100644 index 0000000..236e515 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Components/Stats.razor.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; +using Microsoft.AspNetCore.Components; +using VisionBuilder.UI.Common.ViewModel; + +namespace VisionBuilder.UI.Blazor.Components; + +public partial class Stats : ComponentBase, IDisposable +{ + [Parameter] public StatisticsVM? ViewModel { get; set; } + + private string _sessionStarted = ""; + private string _recipeName = ""; + private string _processingTime = ""; + private int _good; + private int _bad; + private int _total; + private string _errorRate = ""; + private string _statisticsDetails = ""; + private StatisticsVM? _subscribedVm; + + protected override void OnParametersSet() + { + if (_subscribedVm != ViewModel) + { + Unsubscribe(); + Subscribe(); + } + } + + private void Subscribe() + { + _subscribedVm = ViewModel; + if (ViewModel != null) + ViewModel.PropertyChanged += OnPropertyChanged; + } + + private void Unsubscribe() + { + if (_subscribedVm != null) + _subscribedVm.PropertyChanged -= OnPropertyChanged; + _subscribedVm = null; + } + + private async void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + _sessionStarted = ViewModel?.SessionStarted ?? ""; + _recipeName = ViewModel?.RecipeName ?? ""; + _processingTime = ViewModel?.ProcessingTime ?? ""; + _good = ViewModel?.Good ?? 0; + _bad = ViewModel?.Bad ?? 0; + _total = ViewModel?.Total ?? 0; + _errorRate = ViewModel?.ErrorRate ?? ""; + _statisticsDetails = ViewModel?.StatisticsDetails ?? ""; + + await InvokeAsync(StateHasChanged); + } + + public void Dispose() + { + Unsubscribe(); + } +} diff --git a/VisionBuilder.UI.Blazor/Dialogs/ImagePreviewDialog.razor b/VisionBuilder.UI.Blazor/Dialogs/ImagePreviewDialog.razor new file mode 100644 index 0000000..afb38ae --- /dev/null +++ b/VisionBuilder.UI.Blazor/Dialogs/ImagePreviewDialog.razor @@ -0,0 +1,71 @@ +@using MudBlazor +@implements IDisposable + + + + @if (ViewModel != null) + { +
+ @{ + var imageUri = ViewModel.ImageAnalysis?.ToBase64DataUri(); + } + @if (!string.IsNullOrEmpty(imageUri)) + { + @ViewModel.ImageName + } +
+ @ViewModel.ImageName +
+
+ Previous + @(ViewModel.OriginalView ? "Analysis" : "Original") + Next + @if (ViewModel.LearnButtonVisible) + { + Learn + } +
+
+ } +
+ + Close + +
+ +@code { + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; + [Parameter] public ErrorPreviewVM? ViewModel { get; set; } + + protected override void OnParametersSet() + { + if (ViewModel != null) + ViewModel.PropertyChanged += OnPropertyChanged; + } + + private async void OnPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + await InvokeAsync(StateHasChanged); + } + + private void OnPrevious() => ViewModel?.PreviousImageCommand.Execute(null); + private void OnNext() => ViewModel?.NextImageCommand.Execute(null); + private void OnSwitchView() => ViewModel?.SwitchViewCommand.Execute(null); + private async Task OnLearn() + { + if (ViewModel != null) + await ViewModel.Learn(); + } + + private void Close() => MudDialog.Close(); + + public void Dispose() + { + if (ViewModel != null) + ViewModel.PropertyChanged -= OnPropertyChanged; + } +} diff --git a/VisionBuilder.UI.Blazor/Dialogs/PasswordInputDialog.razor b/VisionBuilder.UI.Blazor/Dialogs/PasswordInputDialog.razor new file mode 100644 index 0000000..9cd7edf --- /dev/null +++ b/VisionBuilder.UI.Blazor/Dialogs/PasswordInputDialog.razor @@ -0,0 +1,27 @@ +@using MudBlazor + + + + + + + Cancel + OK + + + +@code { + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; + + private string _password = ""; + + private void Submit() => MudDialog.Close(DialogResult.Ok(_password)); + private void Cancel() => MudDialog.Cancel(); + + private void OnKeyDown(KeyboardEventArgs e) + { + if (e.Key == "Enter") + Submit(); + } +} diff --git a/VisionBuilder.UI.Blazor/Dialogs/RecipeSelectionDialog.razor b/VisionBuilder.UI.Blazor/Dialogs/RecipeSelectionDialog.razor new file mode 100644 index 0000000..3c1e35e --- /dev/null +++ b/VisionBuilder.UI.Blazor/Dialogs/RecipeSelectionDialog.razor @@ -0,0 +1,44 @@ +@using MudBlazor + + + + @if (ViewModel != null) + { +
+ @foreach (var recipe in ViewModel.Recipes) + { + var isSelected = recipe == ViewModel.SelectedRecipe; +
+ @if (recipe.Image != null && !recipe.Image.Empty()) + { + @recipe.RecipeName + } + else + { +
No image
+ } + @recipe.RecipeName +
+ } +
+ } +
+ + Cancel + +
+ +@code { + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; + [Parameter] public RecipeSelectionVM? ViewModel { get; set; } + + private void SelectRecipe(RecipeData recipe) + { + if (ViewModel != null) + ViewModel.SelectedRecipe = recipe; + MudDialog.Close(DialogResult.Ok(true)); + } + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/VisionBuilder.UI.Blazor/Dialogs/SettingsDialog.razor b/VisionBuilder.UI.Blazor/Dialogs/SettingsDialog.razor new file mode 100644 index 0000000..0e8880e --- /dev/null +++ b/VisionBuilder.UI.Blazor/Dialogs/SettingsDialog.razor @@ -0,0 +1,226 @@ +@using MudBlazor +@using System.ComponentModel +@using System.Reflection +@using Inspectron.Settings + + + +
+
+ + + +
+
+ @if (_selectedCategory != null && GetSettingsForCategory(_selectedCategory) is { Count: > 0 } settings) + { + @foreach (var setting in settings) + { +
+ @if (setting.Property.PropertyType == typeof(bool)) + { + var boolVal = (bool)(setting.Value ?? false); + + } + else if (setting.Property.PropertyType == typeof(int)) + { + var intVal = (int)(setting.Value ?? 0); + + } + else if (setting.Property.PropertyType == typeof(double)) + { + var dblVal = (double)(setting.Value ?? 0.0); + + } + else if (setting.Property.PropertyType.IsEnum) + { + + @foreach (var enumVal in Enum.GetValues(setting.Property.PropertyType)) + { + @enumVal.ToString() + } + + } + else + { + var strVal = setting.Value?.ToString() ?? ""; + + } +
+ } + } + else + { + Select a category to view settings. + } +
+
+
+ + Cancel + Save + +
+ +@code { + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; + [Parameter] public InspectronSettings? Settings { get; set; } + + private Dictionary> _categories = new(); + private string? _selectedCategory; + private List _treeRoot = new(); + + protected override void OnParametersSet() + { + if (Settings == null) return; + LoadSettings(); + } + + private void LoadSettings() + { + _categories.Clear(); + _treeRoot = new List(); + var treeView = Settings!.UserSettings; + var root = treeView.Root as Tree; + if (root != null) + Traverse(root, ""); + + // Build the UI tree from category paths + foreach (var categoryPath in _categories.Keys) + { + var parts = categoryPath.Split('/'); + var currentList = _treeRoot; + for (int i = 0; i < parts.Length; i++) + { + var existing = currentList.FirstOrDefault(n => n.Label == parts[i]); + if (existing == null) + { + existing = new CategoryNode + { + Label = parts[i], + FullPath = string.Join("/", parts.Take(i + 1)) + }; + currentList.Add(existing); + } + currentList = existing.ChildNodes; + } + } + } + + private void Traverse(Tree node, string path) + { + object value = node.Value; + + if (value is string folderName) + { + string newPath = string.IsNullOrEmpty(path) ? folderName : $"{path}/{folderName}"; + foreach (Tree child in node.Children) + Traverse(child, newPath); + } + else if (value is InspectronSettings.UserSettingsInfo info) + { + string groupName = info.Name; + string groupPath = string.IsNullOrEmpty(path) ? groupName : $"{path}/{groupName}"; + foreach (PropertyDescriptor pd in info.Settings) + { + if (pd is BoundPropertyDescriptor bpd) + { + var owner = bpd.Owner; + var description = string.IsNullOrEmpty(pd.Description) ? bpd.DisplayName : pd.Description; + var entry = new SettingEntry + { + Owner = owner, + Property = bpd.PropertyInfo, + Description = description, + Value = bpd.PropertyInfo.GetValue(owner) + }; + + if (!_categories.TryGetValue(groupPath, out var list)) + { + list = new List(); + _categories[groupPath] = list; + } + list.Add(entry); + } + } + } + } + + private void OnCategorySelected(string? category) + { + _selectedCategory = category; + } + + private List GetSettingsForCategory(string category) + { + // Exact match first + if (_categories.TryGetValue(category, out var exact)) + return exact; + + // Otherwise aggregate all sub-categories + var prefix = category + "/"; + return _categories + .Where(kvp => kvp.Key.StartsWith(prefix)) + .SelectMany(kvp => kvp.Value) + .ToList(); + } + + private void Save() + { + foreach (var list in _categories.Values) + { + foreach (var setting in list) + { + var currentValue = setting.Property.GetValue(setting.Owner); + if (!Equals(currentValue, setting.Value)) + { + try + { + if (setting.Value != null && setting.Value.GetType() != setting.Property.PropertyType) + { + var converted = Convert.ChangeType(setting.Value, setting.Property.PropertyType); + setting.Property.SetValue(setting.Owner, converted); + } + else + { + setting.Property.SetValue(setting.Owner, setting.Value); + } + } + catch + { + // Skip properties that fail to convert + } + } + } + } + Settings?.SaveSettings(); + MudDialog.Close(DialogResult.Ok(true)); + } + + private void Cancel() => MudDialog.Cancel(); + + private class SettingEntry + { + public object Owner { get; set; } = null!; + public PropertyInfo Property { get; set; } = null!; + public string Description { get; set; } = ""; + public object? Value { get; set; } + } + + public class CategoryNode + { + public string Label { get; set; } = ""; + public string FullPath { get; set; } = ""; + public List ChildNodes { get; set; } = new(); + } +} diff --git a/VisionBuilder.UI.Blazor/Dialogs/SettingsTreeNode.razor b/VisionBuilder.UI.Blazor/Dialogs/SettingsTreeNode.razor new file mode 100644 index 0000000..a7ff8c0 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Dialogs/SettingsTreeNode.razor @@ -0,0 +1,19 @@ +@using MudBlazor + +@foreach (var node in Nodes.OrderBy(n => n.Label)) +{ + @if (node.ChildNodes.Count > 0) + { + + + + } + else + { + + } +} + +@code { + [Parameter] public List Nodes { get; set; } = new(); +} diff --git a/VisionBuilder.UI.Blazor/Helpers/MatExtensions.cs b/VisionBuilder.UI.Blazor/Helpers/MatExtensions.cs new file mode 100644 index 0000000..43be812 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Helpers/MatExtensions.cs @@ -0,0 +1,16 @@ +using OpenCvSharp; + +namespace VisionBuilder.UI.Blazor.Helpers; + +public static class MatExtensions +{ + public static string ToBase64DataUri(this Mat mat, int quality = 75) + { + if (mat == null || mat.Empty()) + return string.Empty; + + Cv2.ImEncode(".jpg", mat, out var buf, + new ImageEncodingParam(ImwriteFlags.JpegQuality, quality)); + return $"data:image/jpeg;base64,{Convert.ToBase64String(buf)}"; + } +} diff --git a/VisionBuilder.UI.Blazor/ModuleExtensions.cs b/VisionBuilder.UI.Blazor/ModuleExtensions.cs new file mode 100644 index 0000000..9647b24 --- /dev/null +++ b/VisionBuilder.UI.Blazor/ModuleExtensions.cs @@ -0,0 +1,24 @@ +using Inspectron.Settings; +using Ninject; +using VisionBuilder.UI.Blazor.Services; +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.Processing; +using VisionBuilder.UI.Common.Services; +using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; +using ISettings = VisionBuilder.UI.Common.ISettings; + +namespace VisionBuilder.UI.Blazor; + +public static class ModuleExtensions +{ + public static IKernel UseBlazorServices(this IKernel self) + { + self.Bind().To().InSingletonScope(); + self.Bind().To().InSingletonScope(); + self.Bind().To().InSingletonScope(); + self.Bind().To().InSingletonScope(); + self.Bind().To().InSingletonScope(); + self.Bind().To().InSingletonScope(); + return self; + } +} diff --git a/VisionBuilder.UI.Blazor/Services/BlazorImagePreviewService.cs b/VisionBuilder.UI.Blazor/Services/BlazorImagePreviewService.cs new file mode 100644 index 0000000..75dc26f --- /dev/null +++ b/VisionBuilder.UI.Blazor/Services/BlazorImagePreviewService.cs @@ -0,0 +1,34 @@ +using MudBlazor; +using VisionBuilder.UI.Blazor.Dialogs; +using VisionBuilder.UI.Common.ViewModel; +using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; + +namespace VisionBuilder.UI.Blazor.Services; + +public class BlazorImagePreviewService : IImagePreviewService +{ + private IDialogService? _dialogService; + + public void SetDialogService(IDialogService dialogService) + { + _dialogService = dialogService; + } + + public async Task ShowImagePreviewAsync(ErrorPreviewVM errorPreviewVm) + { + if (_dialogService == null) + return; + + var parameters = new DialogParameters + { + { x => x.ViewModel, errorPreviewVm } + }; + var options = new DialogOptions + { + MaxWidth = MaxWidth.Large, + FullWidth = true + }; + var dialog = await _dialogService.ShowAsync("Image Preview", parameters, options); + await dialog.Result; + } +} diff --git a/VisionBuilder.UI.Blazor/Services/BlazorLoadingService.cs b/VisionBuilder.UI.Blazor/Services/BlazorLoadingService.cs new file mode 100644 index 0000000..163d0c5 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Services/BlazorLoadingService.cs @@ -0,0 +1,16 @@ +using VisionBuilder.UI.Common.Processing; + +namespace VisionBuilder.UI.Blazor.Services; + +public class BlazorLoadingService : ILoadingService +{ + public void StartLoading(string title) + { + // Loading indicator managed at the Blazor host level if needed. + } + + public void StopLoading(string title) + { + // Loading indicator managed at the Blazor host level if needed. + } +} diff --git a/VisionBuilder.UI.Blazor/Services/BlazorPasswordInputService.cs b/VisionBuilder.UI.Blazor/Services/BlazorPasswordInputService.cs new file mode 100644 index 0000000..2732956 --- /dev/null +++ b/VisionBuilder.UI.Blazor/Services/BlazorPasswordInputService.cs @@ -0,0 +1,27 @@ +using MudBlazor; +using VisionBuilder.UI.Blazor.Dialogs; +using VisionBuilder.UI.Common.Services; + +namespace VisionBuilder.UI.Blazor.Services; + +public class BlazorPasswordInputService : IPasswordInputService +{ + private IDialogService? _dialogService; + + public void SetDialogService(IDialogService dialogService) + { + _dialogService = dialogService; + } + + public async Task GetPasswordAsync() + { + if (_dialogService == null) + return null; + + var dialog = await _dialogService.ShowAsync("Password Required"); + var result = await dialog.Result; + if (result != null && !result.Canceled) + return result.Data as string; + return null; + } +} diff --git a/VisionBuilder.UI.Blazor/Services/BlazorRecipeSelectionDialogService.cs b/VisionBuilder.UI.Blazor/Services/BlazorRecipeSelectionDialogService.cs new file mode 100644 index 0000000..e23b9aa --- /dev/null +++ b/VisionBuilder.UI.Blazor/Services/BlazorRecipeSelectionDialogService.cs @@ -0,0 +1,30 @@ +using MudBlazor; +using VisionBuilder.UI.Blazor.Dialogs; +using VisionBuilder.UI.Common.ViewModel; +using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; + +namespace VisionBuilder.UI.Blazor.Services; + +public class BlazorRecipeSelectionDialogService : IRecipeSelectionDialogService +{ + private IDialogService? _dialogService; + + public void SetDialogService(IDialogService dialogService) + { + _dialogService = dialogService; + } + + public async Task SelectRecipeAsync(RecipeSelectionVM recipeSelectionVm) + { + if (_dialogService == null) + return false; + + var parameters = new DialogParameters + { + { x => x.ViewModel, recipeSelectionVm } + }; + var dialog = await _dialogService.ShowAsync("Select Recipe", parameters); + var result = await dialog.Result; + return result != null && !result.Canceled; + } +} diff --git a/VisionBuilder.UI.Blazor/Services/BlazorSettingsService.cs b/VisionBuilder.UI.Blazor/Services/BlazorSettingsService.cs new file mode 100644 index 0000000..bb04fdc --- /dev/null +++ b/VisionBuilder.UI.Blazor/Services/BlazorSettingsService.cs @@ -0,0 +1,40 @@ +using Inspectron.Settings; +using MudBlazor; +using VisionBuilder.UI.Blazor.Dialogs; +using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; + +namespace VisionBuilder.UI.Blazor.Services; + +public class BlazorSettingsService : ISettingsService +{ + private readonly InspectronSettings _settings; + private IDialogService? _dialogService; + + public BlazorSettingsService(InspectronSettings settings) + { + _settings = settings; + } + + public void SetDialogService(IDialogService dialogService) + { + _dialogService = dialogService; + } + + public async Task ShowSettingsDialogAsync() + { + if (_dialogService == null) + return; + + var parameters = new DialogParameters + { + { x => x.Settings, _settings } + }; + var options = new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true + }; + var dialog = await _dialogService.ShowAsync("Settings", parameters, options); + await dialog.Result; + } +} diff --git a/VisionBuilder.UI.Blazor/VisionBuilder.UI.Blazor.csproj b/VisionBuilder.UI.Blazor/VisionBuilder.UI.Blazor.csproj new file mode 100644 index 0000000..b9e5198 --- /dev/null +++ b/VisionBuilder.UI.Blazor/VisionBuilder.UI.Blazor.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/VisionBuilder.UI.Blazor/_Imports.razor b/VisionBuilder.UI.Blazor/_Imports.razor new file mode 100644 index 0000000..393c0e8 --- /dev/null +++ b/VisionBuilder.UI.Blazor/_Imports.razor @@ -0,0 +1,9 @@ +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Web +@using MudBlazor +@using VisionBuilder.UI.Common +@using VisionBuilder.UI.Common.ViewModel +@using VisionBuilder.UI.Common.ViewModel.Classes +@using VisionBuilder.UI.Blazor.Helpers +@using VisionBuilder.UI.Blazor.Components +@using VisionBuilder.UI.Blazor.Dialogs diff --git a/VisionBuilder.UI.Blazor/wwwroot/css/visionbuilder.css b/VisionBuilder.UI.Blazor/wwwroot/css/visionbuilder.css new file mode 100644 index 0000000..dc8b516 --- /dev/null +++ b/VisionBuilder.UI.Blazor/wwwroot/css/visionbuilder.css @@ -0,0 +1,293 @@ +/* Preview Window */ +.preview-window { + position: relative; + width: 100%; + height: 100%; + background: #000; + display: flex; + align-items: center; + justify-content: center; + border: 5px solid transparent; + box-sizing: border-box; +} + +.preview-window.preview-error { + border-color: #f44336; +} + +.preview-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.preview-placeholder { + color: #666; + font-size: 1.2rem; +} + +/* Stats Panel */ +.stats-panel { + padding: 4px 8px; + font-family: 'Verdana', sans-serif; + font-size: 0.75rem; +} + +.stats-field { + margin-bottom: 2px; +} + +.stats-field-label { + font-weight: 700; + display: block; + margin-top: 6px; +} + +.stats-field-value { + display: block; +} + +.stats-bold { + font-weight: 700; +} + +.stats-details-text { + margin: 0; + white-space: pre-wrap; + font-size: 0.7rem; +} + +/* Error Preview Panel */ +.error-preview-panel { + display: flex; + flex-wrap: wrap; + gap: 4px; + padding: 4px; + overflow-y: auto; + height: 100%; + align-content: flex-start; +} + +.error-card { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + padding: 2px; + transition: background-color 0.2s; + width: 180px; +} + +.error-card:hover { + background-color: #f0f0f0; +} + +.error-thumbnail { + width: 170px; + height: 130px; + object-fit: cover; +} + +.error-title { + font-size: 0.7rem; + text-align: center; + margin-top: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; +} + +/* Single Camera Control */ +.single-camera-control { + display: flex; + flex-direction: column; + height: 100%; + background: #fff; +} + +/* Header row */ +.camera-header { + display: flex; + height: 28px; + align-items: center; + flex-shrink: 0; +} + +.camera-header-preview { + flex: 45; + text-align: center; +} + +.camera-header-errors { + flex: 25; + text-align: center; +} + +.camera-header-stats { + flex: 14; +} + +.camera-header-buttons { + flex: 14; +} + +.camera-label { + font-weight: 700; + font-size: 1rem; +} + +.errors-label { + font-size: 1rem; +} + +/* Body row */ +.camera-body { + display: flex; + flex: 1; + min-height: 0; +} + +.camera-preview-area { + flex: 45; + min-width: 0; + min-height: 0; +} + +.camera-divider { + width: 1px; + background-color: #37474f; + flex-shrink: 0; +} + +.camera-errors-area { + flex: 25; + min-width: 0; + overflow-y: auto; +} + +.camera-stats-area { + flex: 14; + overflow-y: auto; + border-left: none; +} + +.camera-buttons-area { + flex: 14; + display: flex; + flex-direction: column; + padding: 8px; +} + +.camera-buttons-stack { + display: flex; + flex-direction: column; + gap: 8px; +} + +.camera-btn { + height: 64px !important; + font-weight: 700 !important; + font-size: 0.85rem !important; +} + +/* Recipe Selection Dialog */ +.recipe-grid { + display: flex; + flex-wrap: wrap; + gap: 12px; + padding: 8px; +} + +.recipe-item { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + padding: 8px; + border: 2px solid #e0e0e0; + border-radius: 8px; + transition: border-color 0.2s; + width: 150px; +} + +.recipe-item:hover { + border-color: #90caf9; +} + +.recipe-item.recipe-selected { + border-color: #1976d2; + background-color: #e3f2fd; +} + +.recipe-thumbnail { + width: 130px; + height: 100px; + object-fit: cover; + border-radius: 4px; +} + +.recipe-no-image { + display: flex; + align-items: center; + justify-content: center; + background: #eee; + color: #999; + font-size: 0.8rem; +} + +.recipe-name { + margin-top: 6px; + font-size: 0.85rem; + text-align: center; + word-break: break-word; +} + +/* Image Preview Dialog */ +.image-preview-dialog { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} + +.image-preview-img { + max-width: 100%; + max-height: 60vh; + object-fit: contain; +} + +.image-preview-info { + text-align: center; +} + +.image-preview-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + justify-content: center; +} + +/* Settings Dialog */ +.settings-layout { + display: flex; + gap: 16px; + min-height: 400px; +} + +.settings-tree { + min-width: 200px; + border-right: 1px solid #e0e0e0; + padding-right: 8px; + overflow-y: auto; +} + +.settings-content { + flex: 1; + padding: 0 8px; + overflow-y: auto; +} + +.settings-field { + margin-bottom: 12px; +} diff --git a/VisionBuilder.UI.Common/Plugins/PluginLoader.cs b/VisionBuilder.UI.Common/Plugins/PluginLoader.cs index e778cff..8ce4e31 100644 --- a/VisionBuilder.UI.Common/Plugins/PluginLoader.cs +++ b/VisionBuilder.UI.Common/Plugins/PluginLoader.cs @@ -43,7 +43,7 @@ public class PluginLoader:IVisionBuilderModule if (!File.Exists(enabledPluginsPath)) { Log.Warning("Enabled plugins file not found: {EnabledPluginsPath}. All discovered plugins will be disabled by default.", enabledPluginsPath); - File.WriteAllText(enabledPluginsPath, ""); + File.WriteAllText(enabledPluginsPath, ""); } List enabledPlugins = File.ReadAllLines(enabledPluginsPath).ToList(); diff --git a/VisionBuilder.UI.Common/Services/IPasswordInputService.cs b/VisionBuilder.UI.Common/Services/IPasswordInputService.cs index 01c9db8..8e57870 100644 --- a/VisionBuilder.UI.Common/Services/IPasswordInputService.cs +++ b/VisionBuilder.UI.Common/Services/IPasswordInputService.cs @@ -2,5 +2,5 @@ public interface IPasswordInputService { - string GetPassword(); + Task GetPasswordAsync(); } \ No newline at end of file diff --git a/VisionBuilder.UI.Common/ViewModel/ErrorPreviewVM.cs b/VisionBuilder.UI.Common/ViewModel/ErrorPreviewVM.cs index 82d0471..e0f2961 100644 --- a/VisionBuilder.UI.Common/ViewModel/ErrorPreviewVM.cs +++ b/VisionBuilder.UI.Common/ViewModel/ErrorPreviewVM.cs @@ -117,11 +117,11 @@ public partial class ErrorPreviewVM:ObservableObject } [RelayCommand(CanExecute = nameof(LearnButtonVisible))] - public void Learn() + public async Task Learn() { if (!string.IsNullOrEmpty(_uiConfiguration.AdminPassword)) { - var passwordOk= _passwordInputService.GetPassword()==_uiConfiguration.AdminPassword; + var passwordOk = await _passwordInputService.GetPasswordAsync() == _uiConfiguration.AdminPassword; if (!passwordOk) return; } _learningTool.Learn(_errorData.RecipeName, _errorData.ImageOriginal); diff --git a/VisionBuilder.UI.Common/ViewModel/ErrorsVM.cs b/VisionBuilder.UI.Common/ViewModel/ErrorsVM.cs index fa656bf..7d76f3c 100644 --- a/VisionBuilder.UI.Common/ViewModel/ErrorsVM.cs +++ b/VisionBuilder.UI.Common/ViewModel/ErrorsVM.cs @@ -28,13 +28,13 @@ public class ErrorsVM:IEventHandler, IEventHandler Errors { get; set; } = new ObservableCollection(); - public void ShowImage(ErrorData errorData) + public async Task ShowImage(ErrorData errorData) { if (errorData == null) return; - + var vm = new ErrorPreviewVM(this, errorData, errorData.RecipeName, _uiConfiguration, _learningTool, _passwordInputService); - _imagePreviewService.ShowImagePreview(vm); - + await _imagePreviewService.ShowImagePreviewAsync(vm); + } diff --git a/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IImagePreviewService.cs b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IImagePreviewService.cs index 8fff6e4..a12d90d 100644 --- a/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IImagePreviewService.cs +++ b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IImagePreviewService.cs @@ -4,6 +4,6 @@ namespace VisionBuilder.UI.Common.ViewModel.Interfaces.UI; public interface IImagePreviewService { - void ShowImagePreview(ErrorPreviewVM errorPreviewVm); + Task ShowImagePreviewAsync(ErrorPreviewVM errorPreviewVm); } \ No newline at end of file diff --git a/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IRecipeSelectionDialogService.cs b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IRecipeSelectionDialogService.cs index d74d79f..77f767a 100644 --- a/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IRecipeSelectionDialogService.cs +++ b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IRecipeSelectionDialogService.cs @@ -2,5 +2,5 @@ public interface IRecipeSelectionDialogService { - bool SelectRecipe(RecipeSelectionVM recipeSelectionVm); + Task SelectRecipeAsync(RecipeSelectionVM recipeSelectionVm); } \ No newline at end of file diff --git a/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/ISettingsService.cs b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/ISettingsService.cs index cbe0675..d9e2f4a 100644 --- a/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/ISettingsService.cs +++ b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/ISettingsService.cs @@ -2,5 +2,5 @@ public interface ISettingsService { - void ShowSettingsDialog(); + Task ShowSettingsDialogAsync(); } \ No newline at end of file diff --git a/VisionBuilder.UI.Common/ViewModel/MainWindowVM.cs b/VisionBuilder.UI.Common/ViewModel/MainWindowVM.cs index 8b50300..a2f238a 100644 --- a/VisionBuilder.UI.Common/ViewModel/MainWindowVM.cs +++ b/VisionBuilder.UI.Common/ViewModel/MainWindowVM.cs @@ -27,9 +27,9 @@ public partial class MainWindowVM:ObservableObject [RelayCommand] - public void Settings() + public async Task Settings() { - _settingsService.ShowSettingsDialog(); + await _settingsService.ShowSettingsDialogAsync(); } [RelayCommand] diff --git a/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs b/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs index ec295a4..89dcee2 100644 --- a/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs +++ b/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs @@ -118,10 +118,10 @@ namespace VisionBuilder.UI.Common [RelayCommand(CanExecute = nameof(IsNotRunning))] - public void SelectRecipe() + public async Task SelectRecipe() { var vm = GetRecipeSelectionVm(); - if (!_recipeSelectionDialogService.SelectRecipe(vm))return; + if (!await _recipeSelectionDialogService.SelectRecipeAsync(vm))return; ProcessRecipeSelectionVm(vm); } diff --git a/VisionBuilder.UI.Windows.Duo/Form1.cs b/VisionBuilder.UI.Windows.Duo/Form1.cs index fe35fe2..e67f7d6 100644 --- a/VisionBuilder.UI.Windows.Duo/Form1.cs +++ b/VisionBuilder.UI.Windows.Duo/Form1.cs @@ -85,11 +85,11 @@ namespace VisionBuilder.UI.Windows.Duo WindowState = FormWindowState.Minimized; } - private void btnSettings_Click(object sender, EventArgs e) + private async void btnSettings_Click(object sender, EventArgs e) { if (_uiConfiguration.ProtectSettingsWithPassword) { - var passwordOk = _passwordInputService.GetPassword() == _uiConfiguration.AdminPassword; + var passwordOk = await _passwordInputService.GetPasswordAsync() == _uiConfiguration.AdminPassword; if (!passwordOk) return; } diff --git a/VisionBuilder.UI.Windows.Test/Form1.cs b/VisionBuilder.UI.Windows.Test/Form1.cs index 1f4b80e..5ebb77b 100644 --- a/VisionBuilder.UI.Windows.Test/Form1.cs +++ b/VisionBuilder.UI.Windows.Test/Form1.cs @@ -87,11 +87,11 @@ namespace VisionBuilder.UI.Windows.Test WindowState = FormWindowState.Minimized; } - private void btnSettings_Click(object sender, EventArgs e) + private async void btnSettings_Click(object sender, EventArgs e) { if (_uiConfiguration.ProtectSettingsWithPassword) { - var passwordOk = _passwordInputService.GetPassword() == _uiConfiguration.AdminPassword; + var passwordOk = await _passwordInputService.GetPasswordAsync() == _uiConfiguration.AdminPassword; if (!passwordOk) return; } diff --git a/VisionBuilder.UI.Windows/Services/WindowsImagePreviewService.cs b/VisionBuilder.UI.Windows/Services/WindowsImagePreviewService.cs index 6946d83..c04beb1 100644 --- a/VisionBuilder.UI.Windows/Services/WindowsImagePreviewService.cs +++ b/VisionBuilder.UI.Windows/Services/WindowsImagePreviewService.cs @@ -14,12 +14,13 @@ public class WindowsImagePreviewService: IImagePreviewService } public ImagePreview? CurrentWindow { get; private set; } - public void ShowImagePreview(ErrorPreviewVM errorPreviewVm) + public Task ShowImagePreviewAsync(ErrorPreviewVM errorPreviewVm) { var window = new ImagePreview(); window.SetViewModel(errorPreviewVm); CurrentWindow = window; window.ShowDialog(); CurrentWindow = null; + return Task.CompletedTask; } } \ No newline at end of file diff --git a/VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs b/VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs index 3f876d9..5922830 100644 --- a/VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs +++ b/VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs @@ -5,15 +5,15 @@ namespace VisionBuilder.UI.Windows.Settings; public class WindowsPasswordInputService: IPasswordInputService { - public string GetPassword() + public Task GetPasswordAsync() { if (MaterialInputBox.Prompt("Password required", "", out var password, true) == DialogResult.OK) { - return password; + return Task.FromResult(password); } else { - return null; + return Task.FromResult(null); } } } \ No newline at end of file diff --git a/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs b/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs index f8b6194..4060354 100644 --- a/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs +++ b/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs @@ -13,13 +13,9 @@ public class WindowsRecipeSelectionDialogService: IRecipeSelectionDialogService _recipeCreationTool = recipeCreationTool; } - public bool SelectRecipe(RecipeSelectionVM recipeSelectionVm) + public Task SelectRecipeAsync(RecipeSelectionVM recipeSelectionVm) { RecipeSelectionDialog dialog = new RecipeSelectionDialog(recipeSelectionVm, _recipeCreationTool); - if(dialog.ShowDialog() == DialogResult.OK) - { - return true; - } - return false; + return Task.FromResult(dialog.ShowDialog() == DialogResult.OK); } } \ No newline at end of file diff --git a/VisionBuilder.UI.Windows/Services/WindowsSettingsService.cs b/VisionBuilder.UI.Windows/Services/WindowsSettingsService.cs index 1b446b1..b17dc98 100644 --- a/VisionBuilder.UI.Windows/Services/WindowsSettingsService.cs +++ b/VisionBuilder.UI.Windows/Services/WindowsSettingsService.cs @@ -17,7 +17,7 @@ public class WindowsSettingsService: ISettingsService } - public void ShowSettingsDialog() + public Task ShowSettingsDialogAsync() { Thread th = new Thread(() => { @@ -30,5 +30,6 @@ public class WindowsSettingsService: ISettingsService }); th.SetApartmentState(ApartmentState.STA); th.Start(); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/VisionBuilder.UI.sln b/VisionBuilder.UI.sln index a800607..5ec7f93 100644 --- a/VisionBuilder.UI.sln +++ b/VisionBuilder.UI.sln @@ -100,154 +100,476 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CandyboxPlugin", "Plugins\C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Windows.Duo", "VisionBuilder.UI.Windows.Duo\VisionBuilder.UI.Windows.Duo.csproj", "{AD3BC36E-8FC9-4A13-AD6B-606325AFF291}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{FA674B5A-3394-926C-2B1E-70E5B00E4A5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Blazor", "VisionBuilder.UI.Blazor\VisionBuilder.UI.Blazor.csproj", "{0C14CF80-D743-4809-9B9C-2A096FEA2E06}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Blazor.Uno", "VisionBuilder.UI.Blazor.Uno\VisionBuilder.UI.Blazor.Uno.csproj", "{9AE35B63-607E-4009-9FE6-3DF2CD98071F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Debug|x64.ActiveCfg = Debug|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Debug|x64.Build.0 = Debug|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Debug|x86.ActiveCfg = Debug|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Debug|x86.Build.0 = Debug|Any CPU {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Release|Any CPU.Build.0 = Release|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Release|x64.ActiveCfg = Release|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Release|x64.Build.0 = Release|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Release|x86.ActiveCfg = Release|Any CPU + {5AFF312A-EF5F-49ED-BF54-5AC69F7BEC03}.Release|x86.Build.0 = Release|Any CPU {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Debug|x64.ActiveCfg = Debug|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Debug|x64.Build.0 = Debug|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Debug|x86.ActiveCfg = Debug|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Debug|x86.Build.0 = Debug|Any CPU {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Release|Any CPU.ActiveCfg = Release|Any CPU {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Release|Any CPU.Build.0 = Release|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Release|x64.ActiveCfg = Release|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Release|x64.Build.0 = Release|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Release|x86.ActiveCfg = Release|Any CPU + {98781EAC-F3B2-4DEF-AFED-B0FEB8297025}.Release|x86.Build.0 = Release|Any CPU {B62D435C-3572-4B5B-A381-992345D343B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B62D435C-3572-4B5B-A381-992345D343B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Debug|x64.Build.0 = Debug|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Debug|x86.ActiveCfg = Debug|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Debug|x86.Build.0 = Debug|Any CPU {B62D435C-3572-4B5B-A381-992345D343B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {B62D435C-3572-4B5B-A381-992345D343B0}.Release|Any CPU.Build.0 = Release|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Release|x64.ActiveCfg = Release|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Release|x64.Build.0 = Release|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Release|x86.ActiveCfg = Release|Any CPU + {B62D435C-3572-4B5B-A381-992345D343B0}.Release|x86.Build.0 = Release|Any CPU {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Debug|x64.Build.0 = Debug|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Debug|x86.Build.0 = Debug|Any CPU {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Release|Any CPU.Build.0 = Release|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Release|x64.ActiveCfg = Release|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Release|x64.Build.0 = Release|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Release|x86.ActiveCfg = Release|Any CPU + {D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}.Release|x86.Build.0 = Release|Any CPU {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Debug|x64.ActiveCfg = Debug|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Debug|x64.Build.0 = Debug|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Debug|x86.ActiveCfg = Debug|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Debug|x86.Build.0 = Debug|Any CPU {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Release|Any CPU.ActiveCfg = Release|Any CPU {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Release|Any CPU.Build.0 = Release|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Release|x64.ActiveCfg = Release|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Release|x64.Build.0 = Release|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Release|x86.ActiveCfg = Release|Any CPU + {7697A266-A721-4D74-9C5C-0D4F2F6BBF68}.Release|x86.Build.0 = Release|Any CPU {E286CE4C-B68A-94B6-F477-0DBF42358009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E286CE4C-B68A-94B6-F477-0DBF42358009}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Debug|x64.ActiveCfg = Debug|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Debug|x64.Build.0 = Debug|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Debug|x86.ActiveCfg = Debug|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Debug|x86.Build.0 = Debug|Any CPU {E286CE4C-B68A-94B6-F477-0DBF42358009}.Release|Any CPU.ActiveCfg = Release|Any CPU {E286CE4C-B68A-94B6-F477-0DBF42358009}.Release|Any CPU.Build.0 = Release|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Release|x64.ActiveCfg = Release|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Release|x64.Build.0 = Release|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Release|x86.ActiveCfg = Release|Any CPU + {E286CE4C-B68A-94B6-F477-0DBF42358009}.Release|x86.Build.0 = Release|Any CPU {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Debug|x64.Build.0 = Debug|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Debug|x86.Build.0 = Debug|Any CPU {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Release|Any CPU.Build.0 = Release|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Release|x64.ActiveCfg = Release|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Release|x64.Build.0 = Release|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Release|x86.ActiveCfg = Release|Any CPU + {E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5}.Release|x86.Build.0 = Release|Any CPU {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Debug|x64.Build.0 = Debug|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Debug|x86.Build.0 = Debug|Any CPU {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Release|Any CPU.Build.0 = Release|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Release|x64.ActiveCfg = Release|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Release|x64.Build.0 = Release|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Release|x86.ActiveCfg = Release|Any CPU + {8B7FAFEE-4066-483C-9C9C-10D2D596206D}.Release|x86.Build.0 = Release|Any CPU {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Debug|x64.ActiveCfg = Debug|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Debug|x64.Build.0 = Debug|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Debug|x86.ActiveCfg = Debug|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Debug|x86.Build.0 = Debug|Any CPU {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Release|Any CPU.Build.0 = Release|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Release|x64.ActiveCfg = Release|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Release|x64.Build.0 = Release|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Release|x86.ActiveCfg = Release|Any CPU + {C3EFD7A4-2CAD-7610-FEDE-D2E7484ED417}.Release|x86.Build.0 = Release|Any CPU {FC4698F6-C653-427F-9A42-22D07D466A14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC4698F6-C653-427F-9A42-22D07D466A14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Debug|x64.ActiveCfg = Debug|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Debug|x64.Build.0 = Debug|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Debug|x86.ActiveCfg = Debug|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Debug|x86.Build.0 = Debug|Any CPU {FC4698F6-C653-427F-9A42-22D07D466A14}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC4698F6-C653-427F-9A42-22D07D466A14}.Release|Any CPU.Build.0 = Release|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Release|x64.ActiveCfg = Release|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Release|x64.Build.0 = Release|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Release|x86.ActiveCfg = Release|Any CPU + {FC4698F6-C653-427F-9A42-22D07D466A14}.Release|x86.Build.0 = Release|Any CPU {8F6BA34A-D070-372B-DD72-B74905460744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8F6BA34A-D070-372B-DD72-B74905460744}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Debug|x64.ActiveCfg = Debug|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Debug|x64.Build.0 = Debug|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Debug|x86.ActiveCfg = Debug|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Debug|x86.Build.0 = Debug|Any CPU {8F6BA34A-D070-372B-DD72-B74905460744}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F6BA34A-D070-372B-DD72-B74905460744}.Release|Any CPU.Build.0 = Release|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Release|x64.ActiveCfg = Release|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Release|x64.Build.0 = Release|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Release|x86.ActiveCfg = Release|Any CPU + {8F6BA34A-D070-372B-DD72-B74905460744}.Release|x86.Build.0 = Release|Any CPU {4F937F88-70B7-A9E4-834A-D60B104686E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4F937F88-70B7-A9E4-834A-D60B104686E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Debug|x64.Build.0 = Debug|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Debug|x86.Build.0 = Debug|Any CPU {4F937F88-70B7-A9E4-834A-D60B104686E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {4F937F88-70B7-A9E4-834A-D60B104686E0}.Release|Any CPU.Build.0 = Release|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Release|x64.ActiveCfg = Release|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Release|x64.Build.0 = Release|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Release|x86.ActiveCfg = Release|Any CPU + {4F937F88-70B7-A9E4-834A-D60B104686E0}.Release|x86.Build.0 = Release|Any CPU {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Debug|x64.ActiveCfg = Debug|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Debug|x64.Build.0 = Debug|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Debug|x86.ActiveCfg = Debug|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Debug|x86.Build.0 = Debug|Any CPU {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Release|Any CPU.ActiveCfg = Release|Any CPU {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Release|Any CPU.Build.0 = Release|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Release|x64.ActiveCfg = Release|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Release|x64.Build.0 = Release|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Release|x86.ActiveCfg = Release|Any CPU + {0DAFEB64-E123-0974-B77C-3518AFD28D87}.Release|x86.Build.0 = Release|Any CPU {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Debug|x64.ActiveCfg = Debug|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Debug|x64.Build.0 = Debug|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Debug|x86.ActiveCfg = Debug|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Debug|x86.Build.0 = Debug|Any CPU {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Release|Any CPU.ActiveCfg = Release|Any CPU {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Release|Any CPU.Build.0 = Release|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Release|x64.ActiveCfg = Release|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Release|x64.Build.0 = Release|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Release|x86.ActiveCfg = Release|Any CPU + {7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82}.Release|x86.Build.0 = Release|Any CPU {97AF69DC-5061-2643-3CC1-99B51976B05B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {97AF69DC-5061-2643-3CC1-99B51976B05B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Debug|x64.ActiveCfg = Debug|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Debug|x64.Build.0 = Debug|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Debug|x86.ActiveCfg = Debug|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Debug|x86.Build.0 = Debug|Any CPU {97AF69DC-5061-2643-3CC1-99B51976B05B}.Release|Any CPU.ActiveCfg = Release|Any CPU {97AF69DC-5061-2643-3CC1-99B51976B05B}.Release|Any CPU.Build.0 = Release|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Release|x64.ActiveCfg = Release|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Release|x64.Build.0 = Release|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Release|x86.ActiveCfg = Release|Any CPU + {97AF69DC-5061-2643-3CC1-99B51976B05B}.Release|x86.Build.0 = Release|Any CPU {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Debug|x64.ActiveCfg = Debug|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Debug|x64.Build.0 = Debug|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Debug|x86.ActiveCfg = Debug|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Debug|x86.Build.0 = Debug|Any CPU {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Release|Any CPU.Build.0 = Release|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Release|x64.ActiveCfg = Release|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Release|x64.Build.0 = Release|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Release|x86.ActiveCfg = Release|Any CPU + {376797BE-145E-4C48-BD7D-8BF8822A6BC5}.Release|x86.Build.0 = Release|Any CPU {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Debug|x64.ActiveCfg = Debug|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Debug|x64.Build.0 = Debug|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Debug|x86.ActiveCfg = Debug|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Debug|x86.Build.0 = Debug|Any CPU {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Release|Any CPU.Build.0 = Release|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Release|x64.ActiveCfg = Release|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Release|x64.Build.0 = Release|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Release|x86.ActiveCfg = Release|Any CPU + {DBAE2469-ADC5-49CB-B6F5-4F440AF2E32B}.Release|x86.Build.0 = Release|Any CPU {51C2BC65-5E5D-E72A-988D-215E192DE889}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {51C2BC65-5E5D-E72A-988D-215E192DE889}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Debug|x64.ActiveCfg = Debug|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Debug|x64.Build.0 = Debug|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Debug|x86.ActiveCfg = Debug|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Debug|x86.Build.0 = Debug|Any CPU {51C2BC65-5E5D-E72A-988D-215E192DE889}.Release|Any CPU.ActiveCfg = Release|Any CPU {51C2BC65-5E5D-E72A-988D-215E192DE889}.Release|Any CPU.Build.0 = Release|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Release|x64.ActiveCfg = Release|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Release|x64.Build.0 = Release|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Release|x86.ActiveCfg = Release|Any CPU + {51C2BC65-5E5D-E72A-988D-215E192DE889}.Release|x86.Build.0 = Release|Any CPU {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Debug|x64.Build.0 = Debug|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Debug|x86.Build.0 = Debug|Any CPU {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Release|Any CPU.Build.0 = Release|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Release|x64.ActiveCfg = Release|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Release|x64.Build.0 = Release|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Release|x86.ActiveCfg = Release|Any CPU + {1F9BB3BD-65C3-4B96-A880-4908CF6D9754}.Release|x86.Build.0 = Release|Any CPU {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|x64.ActiveCfg = Debug|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|x64.Build.0 = Debug|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|x86.ActiveCfg = Debug|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|x86.Build.0 = Debug|Any CPU {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|Any CPU.Build.0 = Release|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|x64.ActiveCfg = Release|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|x64.Build.0 = Release|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|x86.ActiveCfg = Release|Any CPU + {0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|x86.Build.0 = Release|Any CPU {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|x64.ActiveCfg = Debug|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|x64.Build.0 = Debug|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|x86.Build.0 = Debug|Any CPU {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|Any CPU.Build.0 = Release|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|x64.ActiveCfg = Release|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|x64.Build.0 = Release|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|x86.ActiveCfg = Release|Any CPU + {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|x86.Build.0 = Release|Any CPU {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|x64.Build.0 = Debug|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|x86.Build.0 = Debug|Any CPU {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|Any CPU.Build.0 = Release|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|x64.ActiveCfg = Release|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|x64.Build.0 = Release|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|x86.ActiveCfg = Release|Any CPU + {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|x86.Build.0 = Release|Any CPU {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|x64.ActiveCfg = Debug|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|x64.Build.0 = Debug|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|x86.ActiveCfg = Debug|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|x86.Build.0 = Debug|Any CPU {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|Any CPU.Build.0 = Release|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|x64.ActiveCfg = Release|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|x64.Build.0 = Release|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|x86.ActiveCfg = Release|Any CPU + {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|x86.Build.0 = Release|Any CPU {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Debug|x64.Build.0 = Debug|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Debug|x86.Build.0 = Debug|Any CPU {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Release|Any CPU.ActiveCfg = Release|Any CPU {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Release|Any CPU.Build.0 = Release|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Release|x64.ActiveCfg = Release|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Release|x64.Build.0 = Release|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Release|x86.ActiveCfg = Release|Any CPU + {254841AD-0706-4C92-A364-7E5DC17AF4AD}.Release|x86.Build.0 = Release|Any CPU {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Debug|x64.ActiveCfg = Debug|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Debug|x64.Build.0 = Debug|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Debug|x86.ActiveCfg = Debug|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Debug|x86.Build.0 = Debug|Any CPU {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Release|Any CPU.ActiveCfg = Release|Any CPU {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Release|Any CPU.Build.0 = Release|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Release|x64.ActiveCfg = Release|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Release|x64.Build.0 = Release|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Release|x86.ActiveCfg = Release|Any CPU + {86511F06-AABC-4F1D-AB4E-9763AE4A0EF2}.Release|x86.Build.0 = Release|Any CPU {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Debug|x64.Build.0 = Debug|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Debug|x86.Build.0 = Debug|Any CPU {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Release|Any CPU.Build.0 = Release|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Release|x64.ActiveCfg = Release|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Release|x64.Build.0 = Release|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Release|x86.ActiveCfg = Release|Any CPU + {AD1A176C-B19F-4E60-9935-728722BAE3C1}.Release|x86.Build.0 = Release|Any CPU {DB20082B-60E8-D623-3F3A-04612A929CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DB20082B-60E8-D623-3F3A-04612A929CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Debug|x64.Build.0 = Debug|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Debug|x86.Build.0 = Debug|Any CPU {DB20082B-60E8-D623-3F3A-04612A929CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB20082B-60E8-D623-3F3A-04612A929CD6}.Release|Any CPU.Build.0 = Release|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Release|x64.ActiveCfg = Release|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Release|x64.Build.0 = Release|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Release|x86.ActiveCfg = Release|Any CPU + {DB20082B-60E8-D623-3F3A-04612A929CD6}.Release|x86.Build.0 = Release|Any CPU {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Debug|x64.ActiveCfg = Debug|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Debug|x64.Build.0 = Debug|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Debug|x86.ActiveCfg = Debug|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Debug|x86.Build.0 = Debug|Any CPU {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Release|Any CPU.ActiveCfg = Release|Any CPU {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Release|Any CPU.Build.0 = Release|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Release|x64.ActiveCfg = Release|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Release|x64.Build.0 = Release|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Release|x86.ActiveCfg = Release|Any CPU + {52D6A9AE-D0F1-4C52-B688-E0219169E179}.Release|x86.Build.0 = Release|Any CPU {F7D39916-489A-3583-09A0-175AE82D08B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7D39916-489A-3583-09A0-175AE82D08B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Debug|x64.Build.0 = Debug|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Debug|x86.Build.0 = Debug|Any CPU {F7D39916-489A-3583-09A0-175AE82D08B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7D39916-489A-3583-09A0-175AE82D08B7}.Release|Any CPU.Build.0 = Release|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Release|x64.ActiveCfg = Release|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Release|x64.Build.0 = Release|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Release|x86.ActiveCfg = Release|Any CPU + {F7D39916-489A-3583-09A0-175AE82D08B7}.Release|x86.Build.0 = Release|Any CPU {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|x64.ActiveCfg = Debug|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|x64.Build.0 = Debug|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|x86.ActiveCfg = Debug|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|x86.Build.0 = Debug|Any CPU {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|x64.ActiveCfg = Release|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|x64.Build.0 = Release|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|x86.ActiveCfg = Release|Any CPU + {C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|x86.Build.0 = Release|Any CPU {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|x64.ActiveCfg = Debug|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|x64.Build.0 = Debug|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|x86.ActiveCfg = Debug|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|x86.Build.0 = Debug|Any CPU {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|Any CPU.Build.0 = Release|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|x64.ActiveCfg = Release|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|x64.Build.0 = Release|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|x86.ActiveCfg = Release|Any CPU + {44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|x86.Build.0 = Release|Any CPU {B4949493-C2CD-4786-860D-DAACD39A662D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4949493-C2CD-4786-860D-DAACD39A662D}.Debug|x64.ActiveCfg = Debug|x64 + {B4949493-C2CD-4786-860D-DAACD39A662D}.Debug|x64.Build.0 = Debug|x64 + {B4949493-C2CD-4786-860D-DAACD39A662D}.Debug|x86.ActiveCfg = Debug|x86 + {B4949493-C2CD-4786-860D-DAACD39A662D}.Debug|x86.Build.0 = Debug|x86 {B4949493-C2CD-4786-860D-DAACD39A662D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4949493-C2CD-4786-860D-DAACD39A662D}.Release|x64.ActiveCfg = Release|x64 + {B4949493-C2CD-4786-860D-DAACD39A662D}.Release|x64.Build.0 = Release|x64 + {B4949493-C2CD-4786-860D-DAACD39A662D}.Release|x86.ActiveCfg = Release|x86 + {B4949493-C2CD-4786-860D-DAACD39A662D}.Release|x86.Build.0 = Release|x86 {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|x64.ActiveCfg = Debug|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|x64.Build.0 = Debug|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|x86.ActiveCfg = Debug|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|x86.Build.0 = Debug|Any CPU {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|Any CPU.ActiveCfg = Release|Any CPU {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|Any CPU.Build.0 = Release|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|x64.ActiveCfg = Release|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|x64.Build.0 = Release|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|x86.ActiveCfg = Release|Any CPU + {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|x86.Build.0 = Release|Any CPU {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|x64.ActiveCfg = Debug|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|x64.Build.0 = Debug|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|x86.ActiveCfg = Debug|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|x86.Build.0 = Debug|Any CPU {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|Any CPU.Build.0 = Release|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|x64.ActiveCfg = Release|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|x64.Build.0 = Release|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|x86.ActiveCfg = Release|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|x86.Build.0 = Release|Any CPU {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|x64.ActiveCfg = Debug|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|x64.Build.0 = Debug|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|x86.ActiveCfg = Debug|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|x86.Build.0 = Debug|Any CPU {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|Any CPU.ActiveCfg = Release|Any CPU {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|Any CPU.Build.0 = Release|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|x64.ActiveCfg = Release|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|x64.Build.0 = Release|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|x86.ActiveCfg = Release|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|x86.Build.0 = Release|Any CPU {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Debug|x64.ActiveCfg = Debug|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Debug|x64.Build.0 = Debug|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Debug|x86.ActiveCfg = Debug|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Debug|x86.Build.0 = Debug|Any CPU {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Release|Any CPU.Build.0 = Release|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Release|x64.ActiveCfg = Release|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Release|x64.Build.0 = Release|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Release|x86.ActiveCfg = Release|Any CPU + {AD3BC36E-8FC9-4A13-AD6B-606325AFF291}.Release|x86.Build.0 = Release|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Debug|x64.Build.0 = Debug|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Debug|x86.Build.0 = Debug|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Release|Any CPU.Build.0 = Release|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Release|x64.ActiveCfg = Release|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Release|x64.Build.0 = Release|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Release|x86.ActiveCfg = Release|Any CPU + {0C14CF80-D743-4809-9B9C-2A096FEA2E06}.Release|x86.Build.0 = Release|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Debug|x64.ActiveCfg = Debug|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Debug|x64.Build.0 = Debug|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Debug|x86.ActiveCfg = Debug|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Debug|x86.Build.0 = Debug|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Release|Any CPU.Build.0 = Release|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Release|x64.ActiveCfg = Release|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Release|x64.Build.0 = Release|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Release|x86.ActiveCfg = Release|Any CPU + {9AE35B63-607E-4009-9FE6-3DF2CD98071F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -284,6 +606,8 @@ Global {B20D7E75-1C19-632D-21D2-2663B0329C5E} = {583A77DF-A293-4F3E-AB96-7310BC495822} {31C47198-DEC2-4073-A0C2-220549502CD1} = {583A77DF-A293-4F3E-AB96-7310BC495822} {AD3BC36E-8FC9-4A13-AD6B-606325AFF291} = {F3414823-B70E-435E-B4EA-80ABF4371449} + {0C14CF80-D743-4809-9B9C-2A096FEA2E06} = {FA674B5A-3394-926C-2B1E-70E5B00E4A5C} + {9AE35B63-607E-4009-9FE6-3DF2CD98071F} = {F3414823-B70E-435E-B4EA-80ABF4371449} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677} diff --git a/ui.png b/ui.png new file mode 100644 index 0000000..d900dc4 Binary files /dev/null and b/ui.png differ