Browse Source

Add InstallerDialog

pull/55/head
Ionite 1 year ago
parent
commit
b953d21305
No known key found for this signature in database
  1. 5
      StabilityMatrix.Avalonia/Controls/UserControlBase.cs
  2. 19
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  3. 5
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  4. 393
      StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs
  5. 6
      StabilityMatrix.Avalonia/ViewModels/ProgressViewModel.cs
  6. 232
      StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml
  7. 17
      StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml.cs
  8. 9
      StabilityMatrix.Core/Models/PackageVersionType.cs

5
StabilityMatrix.Avalonia/Controls/UserControlBase.cs

@ -17,8 +17,9 @@ public class UserControlBase : UserControl
{
if (DataContext is not ViewModelBase viewModel) return;
// ReSharper disable once MethodHasAsyncOverload
if (Design.IsDesignMode) return;
viewModel.OnLoaded();
viewModel.OnLoadedAsync().SafeFireAndForget();
viewModel.OnLoadedAsync().Wait();
}
}

19
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -71,6 +71,13 @@ public static class DesignData
}
}, null!, settingsManager, downloadService);
InstallerViewModel = new InstallerViewModel(settingsManager, null!,
downloadService, notificationService, sharedFolders, null!)
{
AvailablePackages = new ObservableCollection<BasePackage>(packageFactory.GetAllAvailablePackages()),
SelectedPackage = packages[0]
};
// Checkpoints page
CheckpointsPageViewModel = new CheckpointsPageViewModel(
sharedFolders, settingsManager, downloadService, modelFinder)
@ -106,11 +113,18 @@ public static class DesignData
FilePath = "~/Models/Lora/model.safetensors",
Title = "Some model"
},
}
},
new(settingsManager, downloadService, modelFinder)
{
Title = "VAE",
DirectoryPath = "Packages/VAE",
CheckpointFiles = new ObservableCollection<CheckpointFile>
{
new()
{
FilePath = "~/Models/VAE/vae_v2.pt",
Title = "Some model",
IsLoading = true
Title = "VAE v2",
}
}
}
@ -160,4 +174,5 @@ public static class DesignData
public static CheckpointBrowserViewModel CheckpointBrowserViewModel { get; }
public static SelectModelVersionViewModel SelectModelVersionViewModel { get; }
public static OneClickInstallViewModel OneClickInstallViewModel { get; }
public static InstallerViewModel InstallerViewModel { get; }
}

5
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -24,6 +24,7 @@
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.0-rc1" />
<PackageReference Include="Markdown.Avalonia" Version="11.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.8" />
<PackageReference Include="NLog" Version="5.2.2" />
@ -62,5 +63,9 @@
<DependentUpon>SelectModelVersionDialog.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Views\Dialogs\InstallerDialog.axaml.cs">
<DependentUpon>InstallerDialog.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project>

393
StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs

@ -0,0 +1,393 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using NLog;
using Octokit;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
using Notification = Avalonia.Controls.Notifications.Notification;
using PackageVersion = StabilityMatrix.Core.Models.PackageVersion;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
public partial class InstallerViewModel : ViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly ISettingsManager settingsManager;
private readonly IPyRunner pyRunner;
private readonly IDownloadService downloadService;
private readonly INotificationService notificationService;
private readonly ISharedFolders sharedFolders;
private readonly IPrerequisiteHelper prerequisiteHelper;
[ObservableProperty] private BasePackage selectedPackage;
[ObservableProperty] private PackageVersion? selectedVersion;
[ObservableProperty] private ObservableCollection<BasePackage>? availablePackages;
[ObservableProperty] private ObservableCollection<GitHubCommit>? availableCommits;
[ObservableProperty] private ObservableCollection<PackageVersion>? availableVersions;
[ObservableProperty] private GitHubCommit? selectedCommit;
[ObservableProperty] private string? releaseNotes;
// [ObservableProperty] private bool isReleaseMode;
// Version types (release or commit)
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ReleaseLabelText),
nameof(IsReleaseMode), nameof(SelectedVersion))]
private PackageVersionType selectedVersionType = PackageVersionType.Commit;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsReleaseModeAvailable))]
private PackageVersionType availableVersionTypes =
PackageVersionType.GithubRelease | PackageVersionType.Commit;
public string ReleaseLabelText => SelectedVersionType == PackageVersionType.GithubRelease
? "Version" : "Branch";
public bool IsReleaseMode
{
get => SelectedVersionType == PackageVersionType.GithubRelease;
set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit;
}
public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
[ObservableProperty] private bool showDuplicateWarning;
[ObservableProperty] private string? installName;
internal event EventHandler? PackageInstalled;
public ProgressViewModel InstallProgress { get; } = new()
{
};
public InstallerViewModel(
ISettingsManager settingsManager,
IPyRunner pyRunner,
IDownloadService downloadService, INotificationService notificationService,
ISharedFolders sharedFolders,
IPrerequisiteHelper prerequisiteHelper)
{
this.settingsManager = settingsManager;
this.pyRunner = pyRunner;
this.downloadService = downloadService;
this.notificationService = notificationService;
this.sharedFolders = sharedFolders;
this.prerequisiteHelper = prerequisiteHelper;
// AvailablePackages and SelectedPackage need to be set in init
}
public override async Task OnLoadedAsync()
{
if (IsReleaseMode)
{
var versions = (await SelectedPackage.GetAllVersions()).ToList();
AvailableVersions = new ObservableCollection<PackageVersion>(versions);
if (!AvailableVersions.Any()) return;
SelectedVersion = AvailableVersions[0];
}
else
{
var branches = (await SelectedPackage.GetAllBranches()).ToList();
AvailableVersions = new ObservableCollection<PackageVersion>(branches.Select(b => new PackageVersion
{
TagName = b.Name,
ReleaseNotesMarkdown = b.Commit.Label
}));
SelectedVersion = AvailableVersions.FirstOrDefault(x =>
x.TagName.ToLowerInvariant() is "master" or "main") ?? AvailableVersions[0];
}
ReleaseNotes = SelectedVersion.ReleaseNotesMarkdown;
}
[RelayCommand]
private async Task Install()
{
await ActuallyInstall();
notificationService.Show(new Notification(
$"Package {SelectedPackage.Name} installed successfully!",
"Success", NotificationType.Success));
OnPackageInstalled();
}
private async Task ActuallyInstall()
{
if (string.IsNullOrWhiteSpace(InstallName))
{
notificationService.Show(new Notification("Package name is empty",
"Please enter a name for the package", NotificationType.Error));
}
await InstallGitIfNecessary();
var libraryDir = new DirectoryPath(settingsManager.LibraryDir, "Packages", InstallName);
SelectedPackage.InstallLocation = $"{settingsManager.LibraryDir}\\Packages\\{InstallName}";
// SelectedPackage.DisplayName = InstallName;
if (!PyRunner.PipInstalled || !PyRunner.VenvInstalled)
{
InstallProgress.Text = "Installing dependencies...";
await pyRunner.Initialize();
if (!PyRunner.PipInstalled)
{
await pyRunner.SetupPip();
}
if (!PyRunner.VenvInstalled)
{
await pyRunner.InstallPackage("virtualenv");
}
}
var version = IsReleaseMode
? await DownloadPackage(SelectedVersion!.TagName, false)
: await DownloadPackage(SelectedCommit!.Sha, true);
await InstallPackage();
InstallProgress.Text = "Setting up shared folder links...";
sharedFolders.SetupLinksForPackage(SelectedPackage, SelectedPackage.InstallLocation);
InstallProgress.Text = "Done";
InstallProgress.IsIndeterminate = false;
InstallProgress.Value = 100;
EventManager.Instance.OnGlobalProgressChanged(100);
var branch = SelectedVersionType == PackageVersionType.GithubRelease ?
null : SelectedVersion!.TagName;
var package = new InstalledPackage
{
DisplayName = SelectedPackage.DisplayName,
LibraryPath = $"Packages\\{InstallName}",
Id = Guid.NewGuid(),
PackageName = SelectedPackage.Name,
PackageVersion = version,
DisplayVersion = GetDisplayVersion(version, branch),
InstalledBranch = branch,
LaunchCommand = SelectedPackage.LaunchCommand,
LastUpdateCheck = DateTimeOffset.Now
};
await using var st = settingsManager.BeginTransaction();
st.Settings.InstalledPackages.Add(package);
st.Settings.ActiveInstalledPackage = package.Id;
InstallProgress.Value = 0;
}
private static string GetDisplayVersion(string version, string? branch)
{
return branch == null ? version : $"{branch}@{version[..7]}";
}
private Task<string?> DownloadPackage(string version, bool isCommitHash)
{
InstallProgress.Text = "Downloading package...";
var progress = new Progress<ProgressReport>(progress =>
{
InstallProgress.IsIndeterminate = progress.IsIndeterminate;
InstallProgress.Value = progress.Percentage;
EventManager.Instance.OnGlobalProgressChanged((int) progress.Percentage);
});
return SelectedPackage.DownloadPackage(version, isCommitHash, progress);
}
private async Task InstallPackage()
{
InstallProgress.Text = "Installing package...";
SelectedPackage.ConsoleOutput += SelectedPackageOnConsoleOutput;
try
{
var progress = new Progress<ProgressReport>(progress =>
{
InstallProgress.IsIndeterminate = progress.IsIndeterminate;
InstallProgress.Value = progress.Percentage;
EventManager.Instance.OnGlobalProgressChanged((int) progress.Percentage);
});
await SelectedPackage.InstallPackage(progress);
}
finally
{
SelectedPackage.ConsoleOutput -= SelectedPackageOnConsoleOutput;
}
}
private void SelectedPackageOnConsoleOutput(object? sender, ProcessOutput e)
{
InstallProgress.Description = e.Text;
}
[RelayCommand]
private async Task ShowPreview()
{
var url = SelectedPackage.PreviewImageUri.ToString();
var imageStream = await downloadService.GetImageStreamFromUrl(url);
var bitmap = new Bitmap(imageStream);
var dialog = new ContentDialog
{
Title = "Test title",
PrimaryButtonText = "Open in Browser",
CloseButtonText = "Close",
Content = new Image
{
Source = bitmap,
Stretch = Stretch.Uniform,
MaxHeight = 500,
HorizontalAlignment = HorizontalAlignment.Center
}
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
ProcessRunner.OpenUrl(url);
}
}
partial void OnSelectedPackageChanged(BasePackage? value)
{
if (value == null) return;
ReleaseNotes = string.Empty;
AvailableVersions?.Clear();
AvailableCommits?.Clear();
AvailableVersionTypes = SelectedPackage.ShouldIgnoreReleases
? PackageVersionType.Commit
: PackageVersionType.GithubRelease | PackageVersionType.Commit;
// Reset selected if not compatible
if (!AvailableVersionTypes.HasFlag(SelectedVersionType))
{
SelectedVersionType = PackageVersionType.Commit;
}
var isReleaseMode = SelectedVersionType == PackageVersionType.GithubRelease;
if (Design.IsDesignMode) return;
Task.Run(async () =>
{
var versions = (await value.GetAllVersions(isReleaseMode)).ToList();
if (!versions.Any()) return;
Dispatcher.UIThread.Post(() =>
{
AvailableVersions = new ObservableCollection<PackageVersion>(versions);
SelectedVersion = AvailableVersions[0];
ReleaseNotes = versions.First().ReleaseNotesMarkdown;
});
if (!isReleaseMode)
{
var commits = await value.GetAllCommits(SelectedVersion!.TagName);
if (commits is null || commits.Count == 0) return;
Dispatcher.UIThread.Post(() =>
{
AvailableCommits = new ObservableCollection<GitHubCommit>(commits);
SelectedCommit = AvailableCommits[0];
SelectedVersion = AvailableVersions?.FirstOrDefault(packageVersion =>
packageVersion.TagName.ToLowerInvariant() is "master" or "main");
});
}
}).SafeFireAndForget();
}
private async Task InstallGitIfNecessary()
{
var progressHandler = new Progress<ProgressReport>(progress =>
{
if (progress.Message != null && progress.Message.Contains("Downloading"))
{
InstallProgress.Text = $"Downloading prerequisites... {progress.Percentage:N0}%";
}
else if (progress.Type == ProgressType.Extract)
{
InstallProgress.Text = $"Installing git... {progress.Percentage:N0}%";
}
else if (progress.Title != null && progress.Title.Contains("Unpacking"))
{
InstallProgress.Text = $"Unpacking resources... {progress.Percentage:N0}%";
}
else
{
InstallProgress.Text = progress.Message;
}
InstallProgress.IsIndeterminate = progress.IsIndeterminate;
InstallProgress.Value = Convert.ToInt32(progress.Percentage);
});
await prerequisiteHelper.InstallAllIfNecessary(progressHandler);
}
partial void OnInstallNameChanged(string? value)
{
ShowDuplicateWarning =
settingsManager.Settings.InstalledPackages.Any(p =>
p.LibraryPath == $"Packages{Path.DirectorySeparatorChar}{value}");
}
partial void OnSelectedVersionChanged(PackageVersion? value)
{
ReleaseNotes = value?.ReleaseNotesMarkdown ?? string.Empty;
if (value == null) return;
SelectedCommit = null;
AvailableCommits?.Clear();
if (!IsReleaseMode)
{
Task.Run(async () =>
{
try
{
var hashes = await SelectedPackage.GetAllCommits(value.TagName);
if (hashes is null) throw new Exception("No commits found");
Dispatcher.UIThread.Post(() =>
{
AvailableCommits = new ObservableCollection<GitHubCommit>(hashes);
SelectedCommit = AvailableCommits[0];
});
}
catch (Exception e)
{
Logger.Warn($"Error getting commits: {e.Message}");
}
}).SafeFireAndForget();
}
}
private void OnPackageInstalled() => PackageInstalled?.Invoke(this, EventArgs.Empty);
}

6
StabilityMatrix.Avalonia/ViewModels/ProgressViewModel.cs

@ -7,10 +7,12 @@ namespace StabilityMatrix.Avalonia.ViewModels;
/// </summary>
public partial class ProgressViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsTextVisible))]
[ObservableProperty, NotifyPropertyChangedFor(nameof(IsTextVisible))]
private string? text;
[ObservableProperty]
private string? description;
[ObservableProperty]
private double value;

232
StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml

@ -0,0 +1,232 @@
<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:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:md="https://github.com/whistyun/Markdown.Avalonia"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs"
xmlns:packages="clr-namespace:StabilityMatrix.Core.Models.Packages;assembly=StabilityMatrix.Core"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:octokit="clr-namespace:Octokit;assembly=Octokit"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
x:DataType="dialogs:InstallerViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
d:DataContext="{x:Static mocks:DesignData.InstallerViewModel}"
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.InstallerDialog">
<controls:UserControlBase.Resources>
</controls:UserControlBase.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="1"
Margin="16,8,16,0"
Orientation="Vertical"
DataContext="{Binding InstallProgress}"
IsVisible="{Binding IsProgressVisible}">
<TextBlock
HorizontalAlignment="Center"
Padding="8"
Text="{Binding Text}" />
<ProgressBar
IsIndeterminate="{Binding IsIndeterminate}"
Maximum="100"
Width="500"
Value="{Binding Value}" />
<TextBlock
FontSize="10"
HorizontalAlignment="Center"
Padding="4"
Text="{Binding Description}"
TextWrapping="Wrap" />
</StackPanel>
<Grid Grid.Row="2" HorizontalAlignment="Left" ColumnDefinitions="Auto,Auto,*">
<ListBox
Margin="16"
ItemsSource="{Binding AvailablePackages}"
SelectedItem="{Binding SelectedPackage, Mode=TwoWay}">
<!--<ListView.Style>
<Style TargetType="ListView">
<Setter Property="Background" Value="#191919" />
</Style>
</ListView.Style>-->
<ListBox.Template>
<ControlTemplate>
<!-- BorderBrush="{KeyboardFocusBorderColorBrush}" -->
<Border
BorderThickness="1"
CornerRadius="5">
<ItemsPresenter />
</Border>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type packages:BasePackage}">
<StackPanel Margin="8" VerticalAlignment="Top">
<TextBlock Margin="0,5,0,5" Text="{Binding DisplayName}" />
<TextBlock Margin="0,0,0,5" Text="{Binding ByAuthor}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel
Grid.Column="1"
Margin="16,16,0,16"
Orientation="Vertical">
<TextBlock
FontSize="24"
FontWeight="Bold"
Text="{Binding SelectedPackage.DisplayName, FallbackValue=Stable Diffusion Web UI}" />
<TextBlock FontSize="12" Text="{Binding SelectedPackage.ByAuthor, FallbackValue=by Automatic111}" />
<Button
Background="Transparent"
BorderBrush="Transparent"
Command="{Binding ShowPreviewCommand}"
Content="UI Preview"
Margin="0,8,0,0">
<!--<Button.Style>
<Style BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{DynamicResource SystemAccentColorSecondary}" />
</Setter.Value>
</Setter>
</Style>
</Button.Style>-->
</Button>
<ui:HyperlinkButton Margin="0,0,0,8" NavigateUri="{Binding SelectedPackage.GithubUrl}">
<TextBlock TextWrapping="Wrap">
<Run Text="GitHub Page:" />
<Run Text="{Binding SelectedPackage.GithubUrl, Mode=OneWay}" TextDecorations="Underline" />
</TextBlock>
</ui:HyperlinkButton>
<StackPanel Orientation="Horizontal">
<ToggleButton
Content="Releases"
IsChecked="{Binding IsReleaseMode}"
IsEnabled="{Binding IsReleaseModeAvailable}" />
<ToggleButton
Content="Branches"
IsChecked="{Binding !IsReleaseMode}"
Margin="8,0,0,0" />
</StackPanel>
<StackPanel Margin="0,16,0,0" Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<Label Content="{Binding ReleaseLabelText, FallbackValue=Version}" />
<ComboBox
ItemsSource="{Binding AvailableVersions}"
MinWidth="200"
SelectedItem="{Binding SelectedVersion}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models:PackageVersion}">
<StackPanel Margin="8,0,0,0" VerticalAlignment="Top">
<TextBlock
Margin="0,4,0,4"
Name="NameTextBlock"
Text="{Binding TagName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<StackPanel
Margin="8,0,0,0"
Orientation="Vertical"
IsVisible="{Binding !IsReleaseMode}">
<Label Content="Commit" />
<ComboBox
ItemsSource="{Binding AvailableCommits}"
MinWidth="100"
SelectedItem="{Binding SelectedCommit}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type octokit:GitHubCommit}">
<StackPanel Margin="8,0,0,0" VerticalAlignment="Top">
<TextBlock
Margin="0,4,0,4"
Name="NameTextBlock"
Text="{Binding Sha}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
<Label Content="Display Name" Margin="0,16,0,0" />
<StackPanel Orientation="Horizontal" IsVisible="{Binding ShowDuplicateWarning}">
<ui:SymbolIcon
Foreground="{DynamicResource ThemeRedColor}"
Margin="8"
Symbol="Alert" />
<TextBlock
Foreground="{DynamicResource ThemeRedColor}"
Margin="0,8,8,8"
TextAlignment="Left"
TextWrapping="Wrap">
<Run Text="An installation with this name already exists." />
<LineBreak />
<Run Text="Please choose a different name or select a different Install Location." />
</TextBlock>
</StackPanel>
<TextBox
Margin="0,0,0,8"
Text="{Binding SelectedPackage.DisplayName}" />
<StackPanel Orientation="Horizontal">
<Button
Command="{Binding InstallCommand}"
Content="Install"
Height="50"
IsEnabled="{Binding !ShowDuplicateWarning}"
Margin="0,16,0,0"
VerticalAlignment="Top"
Width="100" />
<controls:ProgressRing
Height="25"
IsIndeterminate="True"
Margin="8,16,0,0"
VerticalAlignment="Center"
IsVisible="{Binding InstallProgress.IsProgressVisible}"
Width="25" />
<TextBlock
Margin="8,16,0,0"
Text="Installing..."
VerticalAlignment="Center"
IsVisible="{Binding InstallProgress.IsProgressVisible}" />
</StackPanel>
</StackPanel>
<mdxaml:MarkdownScrollViewer
Grid.Column="2"
Margin="16"
Source="{Binding ReleaseNotes, Mode=OneWay}"/>
<ContentPresenter
Grid.Column="0"
Grid.ColumnSpan="3"
x:Name="InstallerContentDialog" />
</Grid>
</Grid>
</controls:UserControlBase>

17
StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml.cs

@ -0,0 +1,17 @@
using Avalonia.Markup.Xaml;
using StabilityMatrix.Avalonia.Controls;
namespace StabilityMatrix.Avalonia.Views.Dialogs;
public partial class InstallerDialog : UserControlBase
{
public InstallerDialog()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

9
StabilityMatrix.Core/Models/PackageVersionType.cs

@ -0,0 +1,9 @@
namespace StabilityMatrix.Core.Models;
[Flags]
public enum PackageVersionType
{
None = 0,
GithubRelease = 1 << 0,
Commit = 1 << 1
}
Loading…
Cancel
Save