Browse Source

nav headers! and buttons!

pull/629/head
JT 9 months ago
parent
commit
fb8f06d611
  1. 13
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  2. 4
      StabilityMatrix.Avalonia/Services/RunningPackageService.cs
  3. 1
      StabilityMatrix.Avalonia/Styles/ThemeColors.axaml
  4. 4
      StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs
  5. 101
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  6. 9
      StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs
  7. 80
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

13
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -98,6 +98,19 @@ public static class DesignData
}, },
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui", LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now LastUpdateCheck = DateTimeOffset.Now
},
new()
{
Id = Guid.NewGuid(),
DisplayName = "Running Comfy",
PackageName = "ComfyUI",
Version = new InstalledPackageVersion
{
InstalledBranch = "master",
InstalledCommitSha = "abc12uwu345568972abaedf7g7e679a98879e879f87ga8"
},
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now
} }
}, },
ActiveInstalledPackageId = activePackageId ActiveInstalledPackageId = activePackageId

4
StabilityMatrix.Avalonia/Services/RunningPackageService.cs

@ -32,7 +32,7 @@ public partial class RunningPackageService(
[ObservableProperty] [ObservableProperty]
private ObservableDictionary<Guid, RunningPackageViewModel> runningPackages = []; private ObservableDictionary<Guid, RunningPackageViewModel> runningPackages = [];
public async Task<Guid?> StartPackage(InstalledPackage installedPackage, string? command = null) public async Task<PackagePair?> StartPackage(InstalledPackage installedPackage, string? command = null)
{ {
var activeInstallName = installedPackage.PackageName; var activeInstallName = installedPackage.PackageName;
var basePackage = string.IsNullOrWhiteSpace(activeInstallName) var basePackage = string.IsNullOrWhiteSpace(activeInstallName)
@ -119,7 +119,7 @@ public partial class RunningPackageService(
var viewModel = new RunningPackageViewModel(runningPackage, console); var viewModel = new RunningPackageViewModel(runningPackage, console);
RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel); RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel);
return runningPackage.InstalledPackage.Id; return runningPackage;
} }
public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) => public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) =>

1
StabilityMatrix.Avalonia/Styles/ThemeColors.axaml

@ -21,6 +21,7 @@
<Color x:Key="ThemeCyanColor">#00BCD4</Color> <Color x:Key="ThemeCyanColor">#00BCD4</Color>
<Color x:Key="ThemeTealColor">#009688</Color> <Color x:Key="ThemeTealColor">#009688</Color>
<Color x:Key="ThemeDarkDarkGreenColor">#2C582C</Color> <Color x:Key="ThemeDarkDarkGreenColor">#2C582C</Color>
<SolidColorBrush x:Key="ThemeDarkDarkGreenColorBrush">#2C582C</SolidColorBrush>
<Color x:Key="ThemeDarkGreenColor">#3A783C</Color> <Color x:Key="ThemeDarkGreenColor">#3A783C</Color>
<Color x:Key="ThemeGreenColor">#4BA04F</Color> <Color x:Key="ThemeGreenColor">#4BA04F</Color>
<Color x:Key="ThemeGreenColorTransparent">#AA4BA04F</Color> <Color x:Key="ThemeGreenColorTransparent">#AA4BA04F</Color>

4
StabilityMatrix.Avalonia/ViewModels/NewPackageManagerViewModel.cs

@ -58,6 +58,10 @@ public partial class NewPackageManagerViewModel : PageViewModelBase
{ {
CurrentPagePath.Add(value); CurrentPagePath.Add(value);
} }
else if (value is RunningPackageViewModel)
{
CurrentPagePath.Add(value);
}
else else
{ {
CurrentPagePath.Clear(); CurrentPagePath.Clear();

101
StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Notifications; using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
@ -38,11 +41,13 @@ public partial class PackageCardViewModel(
IPackageFactory packageFactory, IPackageFactory packageFactory,
INotificationService notificationService, INotificationService notificationService,
ISettingsManager settingsManager, ISettingsManager settingsManager,
INavigationService<MainWindowViewModel> navigationService, INavigationService<NewPackageManagerViewModel> navigationService,
ServiceManager<ViewModelBase> vmFactory, ServiceManager<ViewModelBase> vmFactory,
RunningPackageService runningPackageService RunningPackageService runningPackageService
) : ProgressViewModel ) : ProgressViewModel
{ {
private string webUiUrl = string.Empty;
[ObservableProperty] [ObservableProperty]
private InstalledPackage? package; private InstalledPackage? package;
@ -82,6 +87,12 @@ public partial class PackageCardViewModel(
[ObservableProperty] [ObservableProperty]
private bool canUseExtensions; private bool canUseExtensions;
[ObservableProperty]
private bool isRunning;
[ObservableProperty]
private bool showWebUiButton;
partial void OnPackageChanged(InstalledPackage? value) partial void OnPackageChanged(InstalledPackage? value)
{ {
if (string.IsNullOrWhiteSpace(value?.PackageName)) if (string.IsNullOrWhiteSpace(value?.PackageName))
@ -115,6 +126,12 @@ public partial class PackageCardViewModel(
public override async Task OnLoadedAsync() public override async Task OnLoadedAsync()
{ {
if (Design.IsDesignMode && Package?.DisplayName == "Running Comfy")
{
IsRunning = true;
ShowWebUiButton = true;
}
if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet || Package is not { } currentPackage) if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet || Package is not { } currentPackage)
return; return;
@ -151,14 +168,19 @@ public partial class PackageCardViewModel(
if (Package == null) if (Package == null)
return; return;
var packageId = await runningPackageService.StartPackage(Package); var packagePair = await runningPackageService.StartPackage(Package);
if (packageId != null) if (packagePair != null)
{ {
var vm = runningPackageService.GetRunningPackageViewModel(packageId.Value); IsRunning = true;
packagePair.BasePackage.Exited += BasePackageOnExited;
packagePair.BasePackage.StartupComplete += RunningPackageOnStartupComplete;
var vm = runningPackageService.GetRunningPackageViewModel(packagePair.InstalledPackage.Id);
if (vm != null) if (vm != null)
{ {
navigationService.NavigateTo(vm, new BetterDrillInNavigationTransition()); navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
} }
} }
@ -168,6 +190,69 @@ public partial class PackageCardViewModel(
// EventManager.Instance.OnPackageLaunchRequested(Package.Id); // EventManager.Instance.OnPackageLaunchRequested(Package.Id);
} }
public void NavToConsole()
{
if (Package == null)
return;
var vm = runningPackageService.GetRunningPackageViewModel(Package.Id);
if (vm != null)
{
navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
}
}
public void LaunchWebUi()
{
if (string.IsNullOrEmpty(webUiUrl))
return;
notificationService.TryAsync(
Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)),
"Failed to open URL",
$"{webUiUrl}"
);
}
private void BasePackageOnExited(object? sender, int exitCode)
{
EventManager.Instance.OnRunningPackageStatusChanged(null);
Dispatcher
.UIThread.InvokeAsync(async () =>
{
logger.LogTrace("Process exited ({Code}) at {Time:g}", exitCode, DateTimeOffset.Now);
// Need to wait for streams to finish before detaching handlers
if (sender is BaseGitPackage { VenvRunner: not null } package)
{
var process = package.VenvRunner.Process;
if (process is not null)
{
// Max 5 seconds
var ct = new CancellationTokenSource(5000).Token;
try
{
await process.WaitUntilOutputEOF(ct);
}
catch (OperationCanceledException e)
{
logger.LogWarning("Waiting for process EOF timed out: {Message}", e.Message);
}
}
}
// Detach handlers
if (sender is BasePackage basePackage)
{
basePackage.Exited -= BasePackageOnExited;
basePackage.StartupComplete -= RunningPackageOnStartupComplete;
}
IsRunning = false;
})
.SafeFireAndForget();
}
public async Task Uninstall() public async Task Uninstall()
{ {
if (Package?.LibraryPath == null) if (Package?.LibraryPath == null)
@ -559,4 +644,10 @@ public partial class PackageCardViewModel(
IsSharedModelConfig = false; IsSharedModelConfig = false;
} }
} }
private void RunningPackageOnStartupComplete(object? sender, string e)
{
webUiUrl = e.Replace("0.0.0.0", "127.0.0.1");
ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);
}
} }

9
StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs

@ -1,13 +1,18 @@
using StabilityMatrix.Avalonia.ViewModels.Base; using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models;
using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;
namespace StabilityMatrix.Avalonia.ViewModels; namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(ConsoleOutputPage))] [View(typeof(ConsoleOutputPage))]
public class RunningPackageViewModel(PackagePair runningPackage, ConsoleViewModel console) : ViewModelBase public class RunningPackageViewModel(PackagePair runningPackage, ConsoleViewModel console) : PageViewModelBase
{ {
public PackagePair RunningPackage { get; } = runningPackage; public PackagePair RunningPackage { get; } = runningPackage;
public ConsoleViewModel Console { get; } = console; public ConsoleViewModel Console { get; } = console;
public override string Title => RunningPackage.InstalledPackage.PackageName ?? "Running Package";
public override IconSource IconSource => new SymbolIconSource();
} }

80
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -10,11 +10,17 @@
xmlns:icons="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:icons="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:system="clr-namespace:System;assembly=System.Runtime" xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:avalonia="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:PackageManagerViewModel" x:DataType="viewModels:PackageManagerViewModel"
x:CompileBindings="True" x:CompileBindings="True"
d:DataContext="{x:Static designData:DesignData.PackageManagerViewModel}" d:DataContext="{x:Static designData:DesignData.PackageManagerViewModel}"
x:Class="StabilityMatrix.Avalonia.Views.PackageManagerPage"> x:Class="StabilityMatrix.Avalonia.Views.PackageManagerPage">
<controls:UserControlBase.Resources>
<converters:BooleanChoiceMultiConverter x:Key="BoolChoiceMultiConverter" />
</controls:UserControlBase.Resources>
<Grid Margin="16" RowDefinitions="Auto,*,Auto"> <Grid Margin="16" RowDefinitions="Auto,*,Auto">
<ScrollViewer Grid.Row="1"> <ScrollViewer Grid.Row="1">
@ -27,6 +33,13 @@
<DataTemplate DataType="{x:Type packageManager:PackageCardViewModel}"> <DataTemplate DataType="{x:Type packageManager:PackageCardViewModel}">
<controls:Card Padding="8" <controls:Card Padding="8"
CornerRadius="8"> CornerRadius="8">
<controls:Card.Background>
<MultiBinding Converter="{StaticResource BoolChoiceMultiConverter}">
<Binding Path="IsRunning" />
<DynamicResource ResourceKey="ThemeDarkDarkGreenColorBrush"/>
<DynamicResource ResourceKey="ButtonBackground"/>
</MultiBinding>
</controls:Card.Background>
<Grid RowDefinitions="Auto, Auto, Auto, Auto" <Grid RowDefinitions="Auto, Auto, Auto, Auto"
ColumnDefinitions="*,Auto"> ColumnDefinitions="*,Auto">
@ -221,13 +234,19 @@
Grid.Row="3" Grid.Row="3"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
IsVisible="{Binding IsUpdateAvailable}" IsVisible="{Binding !IsUnknownPackage}"
ColumnDefinitions="*, *"> ColumnDefinitions="*,Auto">
<!-- Launch and update buttons --> <!-- Launch and update buttons -->
<Button Grid.Column="0" Classes="accent" <Button Grid.Column="0" Classes="accent"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Command="{Binding Launch}"> Command="{Binding Launch}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsRunning" />
<Binding Path="!IsUnknownPackage" />
</MultiBinding>
</Button.IsVisible>
<StackPanel Orientation="Horizontal" Margin="0,2,0,2"> <StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-rocket" <icons:Icon Value="fa-solid fa-rocket"
Margin="0,0,8,0" Margin="0,0,8,0"
@ -239,38 +258,47 @@
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Command="{Binding Update}"> Command="{Binding Update}">
<StackPanel Orientation="Horizontal" Margin="0,2,0,2"> <Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="IsUpdateAvailable" />
<Binding Path="!IsRunning" />
</MultiBinding>
</Button.IsVisible>
<StackPanel Orientation="Horizontal"
Margin="10,2">
<icons:Icon Value="fa-solid fa-download" <icons:Icon Value="fa-solid fa-download"
Margin="0,0,8,0" Margin="0,0,8,0"
FontSize="14" /> FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Update}" /> <TextBlock Text="{x:Static lang:Resources.Action_Update}" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button Grid.Column="0" Classes="accent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
IsVisible="{Binding IsRunning}"
Command="{Binding NavToConsole}">
<StackPanel Orientation="Horizontal"
Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-terminal"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="Console"/>
</StackPanel>
</Button>
<Button Grid.Column="1" Classes="accent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
IsVisible="{Binding ShowWebUiButton}"
Command="{Binding LaunchWebUi}">
<StackPanel Orientation="Horizontal" Margin="8,2,8,2">
<icons:Icon Value="fa-solid fa-up-right-from-square"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="Web UI" />
</StackPanel>
</Button>
</Grid> </Grid>
<!-- Big launch button -->
<Button
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
Classes="accent"
VerticalAlignment="Bottom"
Command="{Binding Launch}"
HorizontalAlignment="Stretch">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsUpdateAvailable" />
<Binding Path="!IsUnknownPackage" />
</MultiBinding>
</Button.IsVisible>
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<icons:Icon Value="fa-solid fa-rocket"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Launch}" />
</StackPanel>
</Button>
<!-- Import button (for unknown) --> <!-- Import button (for unknown) -->
<Button Grid.Row="3" Grid.Column="0" Classes="transparent-info" <Button Grid.Row="3" Grid.Column="0" Classes="transparent-info"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"

Loading…
Cancel
Save