web UI
This commit is contained in:
71
VisionBuilder.UI.Blazor/Dialogs/ImagePreviewDialog.razor
Normal file
71
VisionBuilder.UI.Blazor/Dialogs/ImagePreviewDialog.razor
Normal file
@@ -0,0 +1,71 @@
|
||||
@using MudBlazor
|
||||
@implements IDisposable
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (ViewModel != null)
|
||||
{
|
||||
<div class="image-preview-dialog">
|
||||
@{
|
||||
var imageUri = ViewModel.ImageAnalysis?.ToBase64DataUri();
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(imageUri))
|
||||
{
|
||||
<img src="@imageUri" alt="@ViewModel.ImageName" class="image-preview-img" />
|
||||
}
|
||||
<div class="image-preview-info">
|
||||
<MudText Typo="Typo.subtitle1">@ViewModel.ImageName</MudText>
|
||||
</div>
|
||||
<div class="image-preview-actions">
|
||||
<MudButton Variant="Variant.Outlined" Disabled="@(!ViewModel.IsPreviousImageEnabled)"
|
||||
OnClick="OnPrevious">Previous</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
OnClick="OnSwitchView">@(ViewModel.OriginalView ? "Analysis" : "Original")</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Disabled="@(!ViewModel.IsNextImageEnabled)"
|
||||
OnClick="OnNext">Next</MudButton>
|
||||
@if (ViewModel.LearnButtonVisible)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
OnClick="OnLearn">Learn</MudButton>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Close">Close</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
27
VisionBuilder.UI.Blazor/Dialogs/PasswordInputDialog.razor
Normal file
27
VisionBuilder.UI.Blazor/Dialogs/PasswordInputDialog.razor
Normal file
@@ -0,0 +1,27 @@
|
||||
@using MudBlazor
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudTextField @bind-Value="_password" Label="Password" InputType="InputType.Password"
|
||||
Immediate="true" OnKeyDown="OnKeyDown" AutoFocus="true" />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">OK</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
44
VisionBuilder.UI.Blazor/Dialogs/RecipeSelectionDialog.razor
Normal file
44
VisionBuilder.UI.Blazor/Dialogs/RecipeSelectionDialog.razor
Normal file
@@ -0,0 +1,44 @@
|
||||
@using MudBlazor
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@if (ViewModel != null)
|
||||
{
|
||||
<div class="recipe-grid">
|
||||
@foreach (var recipe in ViewModel.Recipes)
|
||||
{
|
||||
var isSelected = recipe == ViewModel.SelectedRecipe;
|
||||
<div class="recipe-item @(isSelected ? "recipe-selected" : "")"
|
||||
@onclick="() => SelectRecipe(recipe)">
|
||||
@if (recipe.Image != null && !recipe.Image.Empty())
|
||||
{
|
||||
<img src="@recipe.Image.ToBase64DataUri(60)" alt="@recipe.RecipeName" class="recipe-thumbnail" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="recipe-thumbnail recipe-no-image">No image</div>
|
||||
}
|
||||
<span class="recipe-name">@recipe.RecipeName</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@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();
|
||||
}
|
||||
226
VisionBuilder.UI.Blazor/Dialogs/SettingsDialog.razor
Normal file
226
VisionBuilder.UI.Blazor/Dialogs/SettingsDialog.razor
Normal file
@@ -0,0 +1,226 @@
|
||||
@using MudBlazor
|
||||
@using System.ComponentModel
|
||||
@using System.Reflection
|
||||
@using Inspectron.Settings
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<div class="settings-layout">
|
||||
<div class="settings-tree">
|
||||
<MudTreeView T="string" SelectedValueChanged="OnCategorySelected" Dense="true">
|
||||
<SettingsTreeNode Nodes="_treeRoot" />
|
||||
</MudTreeView>
|
||||
</div>
|
||||
<div class="settings-content">
|
||||
@if (_selectedCategory != null && GetSettingsForCategory(_selectedCategory) is { Count: > 0 } settings)
|
||||
{
|
||||
@foreach (var setting in settings)
|
||||
{
|
||||
<div class="settings-field">
|
||||
@if (setting.Property.PropertyType == typeof(bool))
|
||||
{
|
||||
var boolVal = (bool)(setting.Value ?? false);
|
||||
<MudSwitch T="bool" Value="boolVal"
|
||||
ValueChanged="@(v => setting.Value = v)"
|
||||
Label="@setting.Description" Color="Color.Primary" />
|
||||
}
|
||||
else if (setting.Property.PropertyType == typeof(int))
|
||||
{
|
||||
var intVal = (int)(setting.Value ?? 0);
|
||||
<MudNumericField T="int" Value="intVal"
|
||||
ValueChanged="@(v => setting.Value = v)"
|
||||
Label="@setting.Description" Variant="Variant.Outlined" />
|
||||
}
|
||||
else if (setting.Property.PropertyType == typeof(double))
|
||||
{
|
||||
var dblVal = (double)(setting.Value ?? 0.0);
|
||||
<MudNumericField T="double" Value="dblVal"
|
||||
ValueChanged="@(v => setting.Value = v)"
|
||||
Label="@setting.Description" Variant="Variant.Outlined" />
|
||||
}
|
||||
else if (setting.Property.PropertyType.IsEnum)
|
||||
{
|
||||
<MudSelect T="object" Value="setting.Value"
|
||||
ValueChanged="@(v => setting.Value = v)"
|
||||
Label="@setting.Description" Variant="Variant.Outlined">
|
||||
@foreach (var enumVal in Enum.GetValues(setting.Property.PropertyType))
|
||||
{
|
||||
<MudSelectItem T="object" Value="@enumVal">@enumVal.ToString()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
else
|
||||
{
|
||||
var strVal = setting.Value?.ToString() ?? "";
|
||||
<MudTextField T="string" Value="strVal"
|
||||
ValueChanged="@(v => setting.Value = v)"
|
||||
Label="@setting.Description" Variant="Variant.Outlined" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="pa-4">Select a category to view settings.</MudText>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Save">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
[Parameter] public InspectronSettings? Settings { get; set; }
|
||||
|
||||
private Dictionary<string, List<SettingEntry>> _categories = new();
|
||||
private string? _selectedCategory;
|
||||
private List<CategoryNode> _treeRoot = new();
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (Settings == null) return;
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
_categories.Clear();
|
||||
_treeRoot = new List<CategoryNode>();
|
||||
var treeView = Settings!.UserSettings;
|
||||
var root = treeView.Root as Tree<object>;
|
||||
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<object> node, string path)
|
||||
{
|
||||
object value = node.Value;
|
||||
|
||||
if (value is string folderName)
|
||||
{
|
||||
string newPath = string.IsNullOrEmpty(path) ? folderName : $"{path}/{folderName}";
|
||||
foreach (Tree<object> 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<SettingEntry>();
|
||||
_categories[groupPath] = list;
|
||||
}
|
||||
list.Add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCategorySelected(string? category)
|
||||
{
|
||||
_selectedCategory = category;
|
||||
}
|
||||
|
||||
private List<SettingEntry> 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<CategoryNode> ChildNodes { get; set; } = new();
|
||||
}
|
||||
}
|
||||
19
VisionBuilder.UI.Blazor/Dialogs/SettingsTreeNode.razor
Normal file
19
VisionBuilder.UI.Blazor/Dialogs/SettingsTreeNode.razor
Normal file
@@ -0,0 +1,19 @@
|
||||
@using MudBlazor
|
||||
|
||||
@foreach (var node in Nodes.OrderBy(n => n.Label))
|
||||
{
|
||||
@if (node.ChildNodes.Count > 0)
|
||||
{
|
||||
<MudTreeViewItem T="string" Value="@node.FullPath" Text="@node.Label" Expanded="true">
|
||||
<SettingsTreeNode Nodes="node.ChildNodes" />
|
||||
</MudTreeViewItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTreeViewItem T="string" Value="@node.FullPath" Text="@node.Label" />
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public List<SettingsDialog.CategoryNode> Nodes { get; set; } = new();
|
||||
}
|
||||
Reference in New Issue
Block a user