JT
9 months ago
17 changed files with 658 additions and 23 deletions
@ -0,0 +1,208 @@
|
||||
using System; |
||||
using System.Threading.Tasks; |
||||
using Avalonia; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Input; |
||||
using Avalonia.Interactivity; |
||||
using Avalonia.Xaml.Interactions.DragAndDrop; |
||||
using Avalonia.Xaml.Interactivity; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Controls; |
||||
|
||||
public class BetterContextDragBehavior : Behavior<Control> |
||||
{ |
||||
private Point _dragStartPoint; |
||||
private PointerEventArgs? _triggerEvent; |
||||
private bool _lock; |
||||
private bool _captured; |
||||
|
||||
public static readonly StyledProperty<object?> ContextProperty = AvaloniaProperty.Register< |
||||
ContextDragBehavior, |
||||
object? |
||||
>(nameof(Context)); |
||||
|
||||
public static readonly StyledProperty<IDragHandler?> HandlerProperty = AvaloniaProperty.Register< |
||||
ContextDragBehavior, |
||||
IDragHandler? |
||||
>(nameof(Handler)); |
||||
|
||||
public static readonly StyledProperty<double> HorizontalDragThresholdProperty = AvaloniaProperty.Register< |
||||
ContextDragBehavior, |
||||
double |
||||
>(nameof(HorizontalDragThreshold), 3); |
||||
|
||||
public static readonly StyledProperty<double> VerticalDragThresholdProperty = AvaloniaProperty.Register< |
||||
ContextDragBehavior, |
||||
double |
||||
>(nameof(VerticalDragThreshold), 3); |
||||
|
||||
public static readonly StyledProperty<string> DataFormatProperty = AvaloniaProperty.Register< |
||||
BetterContextDragBehavior, |
||||
string |
||||
>("DataFormat"); |
||||
|
||||
public string DataFormat |
||||
{ |
||||
get => GetValue(DataFormatProperty); |
||||
set => SetValue(DataFormatProperty, value); |
||||
} |
||||
|
||||
public object? Context |
||||
{ |
||||
get => GetValue(ContextProperty); |
||||
set => SetValue(ContextProperty, value); |
||||
} |
||||
|
||||
public IDragHandler? Handler |
||||
{ |
||||
get => GetValue(HandlerProperty); |
||||
set => SetValue(HandlerProperty, value); |
||||
} |
||||
|
||||
public double HorizontalDragThreshold |
||||
{ |
||||
get => GetValue(HorizontalDragThresholdProperty); |
||||
set => SetValue(HorizontalDragThresholdProperty, value); |
||||
} |
||||
|
||||
public double VerticalDragThreshold |
||||
{ |
||||
get => GetValue(VerticalDragThresholdProperty); |
||||
set => SetValue(VerticalDragThresholdProperty, value); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
protected override void OnAttachedToVisualTree() |
||||
{ |
||||
AssociatedObject?.AddHandler( |
||||
InputElement.PointerPressedEvent, |
||||
AssociatedObject_PointerPressed, |
||||
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||
); |
||||
AssociatedObject?.AddHandler( |
||||
InputElement.PointerReleasedEvent, |
||||
AssociatedObject_PointerReleased, |
||||
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||
); |
||||
AssociatedObject?.AddHandler( |
||||
InputElement.PointerMovedEvent, |
||||
AssociatedObject_PointerMoved, |
||||
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||
); |
||||
AssociatedObject?.AddHandler( |
||||
InputElement.PointerCaptureLostEvent, |
||||
AssociatedObject_CaptureLost, |
||||
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble |
||||
); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
protected override void OnDetachedFromVisualTree() |
||||
{ |
||||
AssociatedObject?.RemoveHandler(InputElement.PointerPressedEvent, AssociatedObject_PointerPressed); |
||||
AssociatedObject?.RemoveHandler(InputElement.PointerReleasedEvent, AssociatedObject_PointerReleased); |
||||
AssociatedObject?.RemoveHandler(InputElement.PointerMovedEvent, AssociatedObject_PointerMoved); |
||||
AssociatedObject?.RemoveHandler(InputElement.PointerCaptureLostEvent, AssociatedObject_CaptureLost); |
||||
} |
||||
|
||||
private async Task DoDragDrop(PointerEventArgs triggerEvent, object? value) |
||||
{ |
||||
var data = new DataObject(); |
||||
data.Set(DataFormat, value!); |
||||
|
||||
var effect = DragDropEffects.None; |
||||
|
||||
if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Alt)) |
||||
{ |
||||
effect |= DragDropEffects.Link; |
||||
} |
||||
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Shift)) |
||||
{ |
||||
effect |= DragDropEffects.Move; |
||||
} |
||||
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Control)) |
||||
{ |
||||
effect |= DragDropEffects.Copy; |
||||
} |
||||
else |
||||
{ |
||||
effect |= DragDropEffects.Move; |
||||
} |
||||
|
||||
await DragDrop.DoDragDrop(triggerEvent, data, effect); |
||||
} |
||||
|
||||
private void Released() |
||||
{ |
||||
_triggerEvent = null; |
||||
_lock = false; |
||||
} |
||||
|
||||
private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e) |
||||
{ |
||||
var properties = e.GetCurrentPoint(AssociatedObject).Properties; |
||||
if (properties.IsLeftButtonPressed) |
||||
{ |
||||
if (e.Source is Control control && AssociatedObject?.DataContext == control.DataContext) |
||||
{ |
||||
_dragStartPoint = e.GetPosition(null); |
||||
_triggerEvent = e; |
||||
_lock = true; |
||||
_captured = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void AssociatedObject_PointerReleased(object? sender, PointerReleasedEventArgs e) |
||||
{ |
||||
if (_captured) |
||||
{ |
||||
if (e.InitialPressMouseButton == MouseButton.Left && _triggerEvent is { }) |
||||
{ |
||||
Released(); |
||||
} |
||||
|
||||
_captured = false; |
||||
} |
||||
} |
||||
|
||||
private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e) |
||||
{ |
||||
var properties = e.GetCurrentPoint(AssociatedObject).Properties; |
||||
if (_captured && properties.IsLeftButtonPressed && _triggerEvent is { }) |
||||
{ |
||||
var point = e.GetPosition(null); |
||||
var diff = _dragStartPoint - point; |
||||
var horizontalDragThreshold = HorizontalDragThreshold; |
||||
var verticalDragThreshold = VerticalDragThreshold; |
||||
|
||||
if (Math.Abs(diff.X) > horizontalDragThreshold || Math.Abs(diff.Y) > verticalDragThreshold) |
||||
{ |
||||
if (_lock) |
||||
{ |
||||
_lock = false; |
||||
} |
||||
else |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
var context = Context ?? AssociatedObject?.DataContext; |
||||
|
||||
Handler?.BeforeDragDrop(sender, _triggerEvent, context); |
||||
|
||||
await DoDragDrop(_triggerEvent, context); |
||||
|
||||
Handler?.AfterDragDrop(sender, _triggerEvent, context); |
||||
|
||||
_triggerEvent = null; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void AssociatedObject_CaptureLost(object? sender, PointerCaptureLostEventArgs e) |
||||
{ |
||||
Released(); |
||||
_captured = false; |
||||
} |
||||
} |
@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text.Json.Serialization; |
||||
using Avalonia.Platform.Storage; |
||||
using StabilityMatrix.Avalonia; |
||||
|
||||
namespace StabilityMatrix.Core.Models.Api.OpenArt; |
||||
|
||||
public class OpenArtMetadata |
||||
{ |
||||
[JsonPropertyName("workflow_id")] |
||||
public string? Id { get; set; } |
||||
|
||||
[JsonPropertyName("workflow_name")] |
||||
public string? Name { get; set; } |
||||
|
||||
[JsonPropertyName("creator")] |
||||
public string? Creator { get; set; } |
||||
|
||||
[JsonPropertyName("thumbnails")] |
||||
public IEnumerable<string>? ThumbnailUrls { get; set; } |
||||
|
||||
[JsonIgnore] |
||||
public string? FirstThumbnail => ThumbnailUrls?.FirstOrDefault(); |
||||
|
||||
[JsonIgnore] |
||||
public List<IStorageFile>? FilePath { get; set; } |
||||
} |
@ -0,0 +1,84 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
using System.Text.Json; |
||||
using System.Threading.Tasks; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Input; |
||||
using Avalonia.Platform.Storage; |
||||
using Avalonia.Xaml.Interactions.DragAndDrop; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using DynamicData; |
||||
using DynamicData.Binding; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
using StabilityMatrix.Avalonia.Views; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Models.Api.OpenArt; |
||||
using StabilityMatrix.Core.Services; |
||||
using Windows.Storage; |
||||
using IStorageFile = Avalonia.Platform.Storage.IStorageFile; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels; |
||||
|
||||
[View(typeof(InstalledWorkflowsPage))] |
||||
[Singleton] |
||||
public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManager) : TabViewModelBase |
||||
{ |
||||
public override string Header => "Installed Workflows"; |
||||
|
||||
private readonly SourceCache<OpenArtMetadata, string> workflowsCache = new(x => x.Id); |
||||
|
||||
[ObservableProperty] |
||||
private IObservableCollection<OpenArtMetadata> displayedWorkflows = |
||||
new ObservableCollectionExtended<OpenArtMetadata>(); |
||||
|
||||
protected override async Task OnInitialLoadedAsync() |
||||
{ |
||||
await base.OnInitialLoadedAsync(); |
||||
|
||||
workflowsCache.Connect().DeferUntilLoaded().Bind(DisplayedWorkflows).Subscribe(); |
||||
|
||||
if (Design.IsDesignMode) |
||||
return; |
||||
|
||||
await LoadInstalledWorkflowsAsync(); |
||||
} |
||||
|
||||
[RelayCommand] |
||||
private async Task LoadInstalledWorkflowsAsync() |
||||
{ |
||||
workflowsCache.Clear(); |
||||
|
||||
foreach ( |
||||
var workflowPath in Directory.EnumerateFiles( |
||||
settingsManager.WorkflowDirectory, |
||||
"*.json", |
||||
SearchOption.AllDirectories |
||||
) |
||||
) |
||||
{ |
||||
try |
||||
{ |
||||
var json = await File.ReadAllTextAsync(workflowPath); |
||||
var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json); |
||||
|
||||
if (metadata?.Id == null) |
||||
{ |
||||
metadata = new OpenArtMetadata |
||||
{ |
||||
Id = Guid.NewGuid().ToString(), |
||||
Name = Path.GetFileNameWithoutExtension(workflowPath) |
||||
}; |
||||
} |
||||
|
||||
metadata.FilePath = [await App.StorageProvider.TryGetFileFromPathAsync(workflowPath)]; |
||||
workflowsCache.AddOrUpdate(metadata); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
Console.WriteLine(e); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using Avalonia.Controls; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using FluentAvalonia.UI.Controls; |
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Avalonia.Languages; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
using StabilityMatrix.Avalonia.Views; |
||||
using StabilityMatrix.Core.Attributes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels; |
||||
|
||||
[View(typeof(WorkflowsPage))] |
||||
[Singleton] |
||||
public partial class WorkflowsPageViewModel : PageViewModelBase |
||||
{ |
||||
public override string Title => Resources.Label_Workflows; |
||||
public override IconSource IconSource => new FASymbolIconSource { Symbol = "fa-solid fa-circle-nodes" }; |
||||
|
||||
public IReadOnlyList<TabItem> Pages { get; } |
||||
|
||||
[ObservableProperty] |
||||
private TabItem? selectedPage; |
||||
|
||||
/// <inheritdoc/> |
||||
public WorkflowsPageViewModel( |
||||
OpenArtBrowserViewModel openArtBrowserViewModel, |
||||
InstalledWorkflowsViewModel installedWorkflowsViewModel |
||||
) |
||||
{ |
||||
Pages = new List<TabItem>( |
||||
new List<TabViewModelBase>([openArtBrowserViewModel, installedWorkflowsViewModel]).Select( |
||||
vm => new TabItem { Header = vm.Header, Content = vm } |
||||
) |
||||
); |
||||
SelectedPage = Pages.FirstOrDefault(); |
||||
} |
||||
} |
@ -0,0 +1,189 @@
|
||||
<controls:UserControlBase xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" |
||||
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:openArt="clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt" |
||||
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" |
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
d:DataContext="{x:Static designData:DesignData.InstalledWorkflowsViewModel}" |
||||
x:DataType="viewModels:InstalledWorkflowsViewModel" |
||||
x:Class="StabilityMatrix.Avalonia.Views.InstalledWorkflowsPage"> |
||||
<UserControl.Styles> |
||||
<Style Selector="Border#HoverBorder"> |
||||
<Setter Property="Transitions"> |
||||
<Transitions> |
||||
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237" /> |
||||
</Transitions> |
||||
</Setter> |
||||
|
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="Transitions"> |
||||
<Transitions> |
||||
<TransformOperationsTransition Property="RenderTransform" |
||||
Duration="0:0:0.237"> |
||||
<TransformOperationsTransition.Easing> |
||||
<QuadraticEaseInOut /> |
||||
</TransformOperationsTransition.Easing> |
||||
</TransformOperationsTransition> |
||||
</Transitions> |
||||
</Setter> |
||||
</Style> |
||||
|
||||
<Style Selector="^:pointerover"> |
||||
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000" /> |
||||
<Setter Property="Cursor" Value="Hand" /> |
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="CornerRadius" Value="12" /> |
||||
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)" /> |
||||
</Style> |
||||
<Style Selector="^ Border#ModelCardBottom"> |
||||
<Setter Property="Background" Value="#CC000000" /> |
||||
</Style> |
||||
</Style> |
||||
|
||||
<Style Selector="^:not(:pointerover)"> |
||||
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000" /> |
||||
<Setter Property="Cursor" Value="Arrow" /> |
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="CornerRadius" Value="8" /> |
||||
<Setter Property="RenderTransform" Value="scale(1, 1)" /> |
||||
</Style> |
||||
<Style Selector="^ Border#ModelCardBottom"> |
||||
<Setter Property="Background" Value="#99000000" /> |
||||
</Style> |
||||
</Style> |
||||
</Style> |
||||
</UserControl.Styles> |
||||
|
||||
<Grid RowDefinitions="Auto, *"> |
||||
<controls1:CommandBar Grid.Row="0" Grid.Column="0" |
||||
VerticalAlignment="Center" |
||||
HorizontalAlignment="Left" |
||||
VerticalContentAlignment="Center" |
||||
DefaultLabelPosition="Right"> |
||||
<controls1:CommandBar.PrimaryCommands> |
||||
<controls1:CommandBarButton |
||||
IconSource="Refresh" |
||||
VerticalAlignment="Center" |
||||
Label="{x:Static lang:Resources.Action_Refresh}" |
||||
Command="{Binding LoadInstalledWorkflowsCommand}" /> |
||||
|
||||
<controls1:CommandBarSeparator /> |
||||
|
||||
<controls1:CommandBarElementContainer> |
||||
<StackPanel Orientation="Horizontal"> |
||||
<controls1:SymbolIcon Symbol="Important" |
||||
Margin="8,0" |
||||
FontSize="20"/> |
||||
<TextBlock Text="Drag & drop one of the cards below into ComfyUI to load the workflow" |
||||
VerticalAlignment="Center" |
||||
Margin="0,-2,0,0"/> |
||||
</StackPanel> |
||||
</controls1:CommandBarElementContainer> |
||||
</controls1:CommandBar.PrimaryCommands> |
||||
</controls1:CommandBar> |
||||
|
||||
<ScrollViewer Grid.Column="0" |
||||
Grid.Row="1"> |
||||
<ItemsRepeater ItemsSource="{Binding DisplayedWorkflows}"> |
||||
<ItemsRepeater.Layout> |
||||
<!-- <UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4"/> --> |
||||
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4" /> |
||||
</ItemsRepeater.Layout> |
||||
<ItemsRepeater.ItemTemplate> |
||||
<DataTemplate x:DataType="{x:Type openArt:OpenArtMetadata}"> |
||||
<Border |
||||
Name="HoverBorder" |
||||
Padding="0" |
||||
BorderThickness="0" |
||||
Margin="8" |
||||
ClipToBounds="True" |
||||
CornerRadius="8"> |
||||
<Interaction.Behaviors> |
||||
<BehaviorCollection> |
||||
<controls:BetterContextDragBehavior |
||||
Context="{Binding FilePath}" |
||||
DataFormat="Files" |
||||
HorizontalDragThreshold="6" |
||||
VerticalDragThreshold="6" /> |
||||
</BehaviorCollection> |
||||
</Interaction.Behaviors> |
||||
<Button |
||||
Name="ModelCard" |
||||
Classes="transparent-full" |
||||
Padding="0" |
||||
BorderThickness="0" |
||||
VerticalContentAlignment="Top" |
||||
CornerRadius="8"> |
||||
<Grid RowDefinitions="*, Auto"> |
||||
<controls:BetterAdvancedImage |
||||
Grid.Row="0" |
||||
Grid.RowSpan="2" |
||||
CornerRadius="8" |
||||
Width="330" |
||||
Height="400" |
||||
Source="{Binding FirstThumbnail}" |
||||
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}" |
||||
Stretch="UniformToFill" |
||||
StretchDirection="Both" /> |
||||
|
||||
<avalonia:Icon Grid.Row="0" |
||||
Grid.RowSpan="2" |
||||
CornerRadius="8" |
||||
Width="330" |
||||
Height="400" |
||||
FontSize="100" |
||||
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNull}, FallbackValue=False}" |
||||
Value="fa-regular fa-file-code"/> |
||||
|
||||
<!-- Username pill card --> |
||||
<Border |
||||
BoxShadow="inset 1.2 0 80 1.8 #66000000" |
||||
CornerRadius="16" |
||||
Margin="4" |
||||
Grid.Row="0" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Bottom"> |
||||
<Border.Resources> |
||||
<DropShadowEffect |
||||
x:Key="TextDropShadowEffect" |
||||
BlurRadius="12" |
||||
Color="#FF000000" |
||||
Opacity="0.9" /> |
||||
</Border.Resources> |
||||
<TextBlock |
||||
VerticalAlignment="Center" |
||||
Effect="{StaticResource TextDropShadowEffect}" |
||||
Text="{Binding Creator}" /> |
||||
</Border> |
||||
|
||||
<Border |
||||
Name="ModelCardBottom" |
||||
Grid.Row="1"> |
||||
<TextBlock |
||||
Padding="16" |
||||
Margin="8,0,8,0" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontWeight="SemiBold" |
||||
Foreground="{DynamicResource TextControlForeground}" |
||||
LetterSpacing="0.33" |
||||
TextWrapping="Wrap" |
||||
Text="{Binding Name}" |
||||
ToolTip.Tip="{Binding Name}" /> |
||||
</Border> |
||||
</Grid> |
||||
</Button> |
||||
</Border> |
||||
</DataTemplate> |
||||
</ItemsRepeater.ItemTemplate> |
||||
</ItemsRepeater> |
||||
</ScrollViewer> |
||||
</Grid> |
||||
</controls:UserControlBase> |
@ -0,0 +1,13 @@
|
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Core.Attributes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views; |
||||
|
||||
[Singleton] |
||||
public partial class InstalledWorkflowsPage : UserControlBase |
||||
{ |
||||
public InstalledWorkflowsPage() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
<controls:UserControlBase xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
x:DataType="viewModels:WorkflowsPageViewModel" |
||||
x:Class="StabilityMatrix.Avalonia.Views.WorkflowsPage"> |
||||
<TabControl ItemsSource="{Binding Pages}" |
||||
SelectedItem="{Binding SelectedPage}"/> |
||||
</controls:UserControlBase> |
@ -0,0 +1,13 @@
|
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Core.Attributes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views; |
||||
|
||||
[Singleton] |
||||
public partial class WorkflowsPage : UserControlBase |
||||
{ |
||||
public WorkflowsPage() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
} |
Loading…
Reference in new issue