Browse Source

Merge branch 'main' of https://github.com/ionite34/StabilityMatrix into combine-jenkinsfile

pull/55/head
JT 1 year ago
parent
commit
ae7ed16bea
  1. 6
      StabilityMatrix.Avalonia/Assets.cs
  2. 32
      StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs
  3. 64
      StabilityMatrix.Avalonia/Helpers/WindowsShortcuts.cs
  4. 10
      StabilityMatrix.Avalonia/Program.cs
  5. 2
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  6. 179
      StabilityMatrix.Avalonia/ViewModels/CheckpointFolder.cs
  7. 2
      StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs
  8. 119
      StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
  9. 40
      StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
  10. 21
      StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs
  11. 154
      StabilityMatrix.Avalonia/Views/SettingsPage.axaml
  12. 11
      StabilityMatrix.Core/Helper/Compat.cs
  13. 15
      StabilityMatrix.Core/Models/FileInterfaces/TempDirectoryPath.cs

6
StabilityMatrix.Avalonia/Assets.cs

@ -10,6 +10,12 @@ namespace StabilityMatrix.Avalonia;
internal static class Assets
{
public static AvaloniaResource AppIcon { get; } =
new("avares://StabilityMatrix.Avalonia/Assets/Icon.ico");
public static AvaloniaResource AppIconPng { get; } =
new("avares://StabilityMatrix.Avalonia/Assets/Icon.png");
/// <summary>
/// Fixed image for models with no images.
/// </summary>

32
StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs

@ -0,0 +1,32 @@
using System.Diagnostics;
using System.Linq;
using System.Runtime.Versioning;
using System.Threading.Tasks;
namespace StabilityMatrix.Avalonia.Helpers;
[SupportedOSPlatform("windows")]
public static class WindowsElevated
{
/// <summary>
/// Move a file from source to target using elevated privileges.
/// </summary>
public static async Task<int> MoveFiles(params (string sourcePath, string targetPath)[] moves)
{
// Combine into single command
var args = string.Join(" & ", moves.Select(
x => $"move \"{x.sourcePath}\" \"{x.targetPath}\""));
using var process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = $"/c {args}";
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.Verb = "runas";
process.Start();
await process.WaitForExitAsync().ConfigureAwait(false);
return process.ExitCode;
}
}

64
StabilityMatrix.Avalonia/Helpers/WindowsShortcuts.cs

@ -0,0 +1,64 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Versioning;
using System.Text;
namespace StabilityMatrix.Avalonia.Helpers;
[SupportedOSPlatform("windows")]
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public static class WindowsShortcuts
{
public static void CreateShortcut(
string shortcutPath,
string targetPath,
string iconPath,
string description)
{
// ReSharper disable once SuspiciousTypeConversion.Global
var link = (IShellLink) new ShellLink();
// setup shortcut information
link.SetDescription(description);
link.SetPath(targetPath);
link.SetIconLocation(iconPath, 0);
// ReSharper disable once SuspiciousTypeConversion.Global
var file = (IPersistFile) link;
file.Save(shortcutPath, false);
}
[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
private class ShellLink
{
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
private interface IShellLink
{
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags);
void GetIDList(out IntPtr ppidl);
void SetIDList(IntPtr pidl);
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
void GetHotkey(out short pwHotkey);
void SetHotkey(short wHotkey);
void GetShowCmd(out int piShowCmd);
void SetShowCmd(int iShowCmd);
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
void Resolve(IntPtr hwnd, int fFlags);
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
}

10
StabilityMatrix.Avalonia/Program.cs

@ -32,6 +32,8 @@ public class Program
{
public static AppArgs Args { get; } = new();
public static bool IsDebugBuild { get; private set; }
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
@ -42,6 +44,8 @@ public class Program
Args.DebugSentry = args.Contains("--debug-sentry");
Args.NoSentry = args.Contains("--no-sentry");
Args.NoWindowChromeEffects = args.Contains("--no-window-chrome-effects");
SetDebugBuild();
HandleUpdateReplacement();
@ -215,4 +219,10 @@ public class Program
Dispatcher.UIThread.InvokeShutdown();
Environment.Exit(Marshal.GetHRForException(exception));
}
[Conditional("DEBUG")]
private static void SetDebugBuild()
{
IsDebugBuild = true;
}
}

2
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -8,7 +8,7 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationIcon>./Assets/Icon.ico</ApplicationIcon>
<Version>2.0.0-dev.1</Version>
<Version>2.0.3-dev.1</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>

179
StabilityMatrix.Avalonia/ViewModels/CheckpointFolder.cs

@ -198,9 +198,11 @@ public partial class CheckpointFolder : ViewModelBase
var textBox = new TextBox();
var dialog = new ContentDialog
{
Title = "Folder name",
Content = textBox,
DefaultButton = ContentDialogButton.Primary,
PrimaryButtonText = "OK",
PrimaryButtonText = "Create",
CloseButtonText = "Cancel",
IsPrimaryButtonEnabled = true,
};
@ -232,106 +234,112 @@ public partial class CheckpointFolder : ViewModelBase
/// </summary>
public async Task ImportFilesAsync(IEnumerable<string> files, bool convertToConnected = false)
{
Progress.Value = 0;
var copyPaths = files.ToDictionary(k => k, v => Path.Combine(DirectoryPath, Path.GetFileName(v)));
var progress = new Progress<ProgressReport>(report =>
{
Progress.IsIndeterminate = false;
Progress.Value = report.Percentage;
// For multiple files, add count
Progress.Text = copyPaths.Count > 1 ? $"Importing {report.Title} ({report.Message})" : $"Importing {report.Title}";
});
await FileTransfers.CopyFiles(copyPaths, progress);
// Hash files and convert them to connected model if found
if (convertToConnected)
try
{
var modelFilesCount = copyPaths.Count;
var modelFiles = copyPaths.Values
.Select(path => new FilePath(path));
Progress.Value = 0;
var copyPaths = files.ToDictionary(k => k, v => Path.Combine(DirectoryPath, Path.GetFileName(v)));
// Holds tasks for model queries after hash
var modelQueryTasks = new List<Task<bool>>();
var progress = new Progress<ProgressReport>(report =>
{
Progress.IsIndeterminate = false;
Progress.Value = report.Percentage;
// For multiple files, add count
Progress.Text = copyPaths.Count > 1 ? $"Importing {report.Title} ({report.Message})" : $"Importing {report.Title}";
});
foreach (var (i, modelFile) in modelFiles.Enumerate())
await FileTransfers.CopyFiles(copyPaths, progress);
// Hash files and convert them to connected model if found
if (convertToConnected)
{
var hashProgress = new Progress<ProgressReport>(report =>
{
Progress.IsIndeterminate = false;
Progress.Value = report.Percentage;
Progress.Text = modelFilesCount > 1 ?
$"Computing metadata for {modelFile.Info.Name} ({i}/{modelFilesCount})" :
$"Computing metadata for {report.Title}";
});
var hashBlake3 = await FileHash.GetBlake3Async(modelFile, hashProgress);
var modelFilesCount = copyPaths.Count;
var modelFiles = copyPaths.Values
.Select(path => new FilePath(path));
// Start a task to query the model in background
var queryTask = Task.Run(async () =>
// Holds tasks for model queries after hash
var modelQueryTasks = new List<Task<bool>>();
foreach (var (i, modelFile) in modelFiles.Enumerate())
{
var result = await modelFinder.LocalFindModel(hashBlake3);
result ??= await modelFinder.RemoteFindModel(hashBlake3);
var hashProgress = new Progress<ProgressReport>(report =>
{
Progress.IsIndeterminate = report.IsIndeterminate;
Progress.Value = report.Percentage;
Progress.Text = modelFilesCount > 1 ?
$"Computing metadata for {modelFile.Name} ({i}/{modelFilesCount})" :
$"Computing metadata for {modelFile.Name}";
});
var hashBlake3 = await FileHash.GetBlake3Async(modelFile, hashProgress);
// Start a task to query the model in background
var queryTask = Task.Run(async () =>
{
var result = await modelFinder.LocalFindModel(hashBlake3);
result ??= await modelFinder.RemoteFindModel(hashBlake3);
if (result is null) return false; // Not found
if (result is null) return false; // Not found
var (model, version, file) = result.Value;
// Save connected model info json
var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Info.Name);
var modelInfo = new ConnectedModelInfo(
model, version, file, DateTimeOffset.UtcNow);
await modelInfo.SaveJsonToDirectory(DirectoryPath, modelFileName);
var (model, version, file) = result.Value;
// Save connected model info json
var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Info.Name);
var modelInfo = new ConnectedModelInfo(
model, version, file, DateTimeOffset.UtcNow);
await modelInfo.SaveJsonToDirectory(DirectoryPath, modelFileName);
// If available, save thumbnail
var image = version.Images?.FirstOrDefault();
if (image != null)
{
var imageExt = Path.GetExtension(image.Url).TrimStart('.');
if (imageExt is "jpg" or "jpeg" or "png")
// If available, save thumbnail
var image = version.Images?.FirstOrDefault();
if (image != null)
{
var imageDownloadPath = Path.GetFullPath(
Path.Combine(DirectoryPath, $"{modelFileName}.preview.{imageExt}"));
await downloadService.DownloadToFileAsync(image.Url, imageDownloadPath);
var imageExt = Path.GetExtension(image.Url).TrimStart('.');
if (imageExt is "jpg" or "jpeg" or "png")
{
var imageDownloadPath = Path.GetFullPath(
Path.Combine(DirectoryPath, $"{modelFileName}.preview.{imageExt}"));
await downloadService.DownloadToFileAsync(image.Url, imageDownloadPath);
}
}
}
return true;
});
modelQueryTasks.Add(queryTask);
}
// Set progress to indeterminate
Progress.IsIndeterminate = true;
Progress.Text = "Checking connected model information";
// Wait for all model queries to finish
var modelQueryResults = await Task.WhenAll(modelQueryTasks);
var successCount = modelQueryResults.Count(r => r);
var totalCount = modelQueryResults.Length;
var failCount = totalCount - successCount;
return true;
});
modelQueryTasks.Add(queryTask);
}
// Set progress to indeterminate
Progress.IsIndeterminate = true;
Progress.Text = "Checking connected model information";
// Wait for all model queries to finish
var modelQueryResults = await Task.WhenAll(modelQueryTasks);
var successCount = modelQueryResults.Count(r => r);
var totalCount = modelQueryResults.Length;
var failCount = totalCount - successCount;
await IndexAsync();
Progress.Value = 100;
Progress.Text = successCount switch
await IndexAsync();
Progress.Value = 100;
Progress.Text = successCount switch
{
0 when failCount > 0 =>
"Import complete. No connected data found.",
> 0 when failCount > 0 =>
$"Import complete. Found connected data for {successCount} of {totalCount} models.",
1 when failCount == 0 =>
"Import complete. Found connected data for 1 model.",
_ => $"Import complete. Found connected data for all {totalCount} models."
};
}
else
{
0 when failCount > 0 =>
"Import complete. No connected data found.",
> 0 when failCount > 0 =>
$"Import complete. Found connected data for {successCount} of {totalCount} models.",
_ => $"Import complete. Found connected data for all {totalCount} models."
};
DelayedClearProgress(TimeSpan.FromSeconds(1.5));
Progress.Text = "Import complete";
Progress.Value = 100;
await IndexAsync();
}
}
else
finally
{
Progress.Text = "Import complete";
Progress.Value = 100;
await IndexAsync();
DelayedClearProgress(TimeSpan.FromSeconds(1.5));
}
}
@ -345,6 +353,7 @@ public partial class CheckpointFolder : ViewModelBase
{
IsImportInProgress = false;
Progress.Value = 0;
Progress.IsIndeterminate = false;
Progress.Text = string.Empty;
});
}

2
StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs

@ -68,7 +68,7 @@ public partial class ConsoleViewModel : ObservableObject, IDisposable
throw new InvalidOperationException("Update task is already running");
}
updateCts = new CancellationTokenSource();
updateTask = Dispatcher.UIThread.InvokeAsync(ConsoleUpdateLoop, DispatcherPriority.Render);
updateTask = Dispatcher.UIThread.InvokeAsync(ConsoleUpdateLoop, DispatcherPriority.Send);
}
/// <summary>

119
StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Text;
@ -12,6 +13,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using NLog;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Helpers;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.Views;
@ -19,6 +22,7 @@ using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
using Symbol = FluentIcons.Common.Symbol;
@ -41,7 +45,8 @@ public partial class SettingsViewModel : PageViewModelBase
public override IconSource IconSource => new SymbolIconSource {Symbol = Symbol.Settings, IsFilled = true};
// ReSharper disable once MemberCanBeMadeStatic.Global
public string AppVersion => $"Version {Compat.AppVersion}";
public string AppVersion => $"Version {Compat.AppVersion}" +
(Program.IsDebugBuild ? " (Debug)" : "");
// Theme section
[ObservableProperty] private string? selectedTheme;
@ -80,8 +85,12 @@ public partial class SettingsViewModel : PageViewModelBase
this.pyRunner = pyRunner;
SharedState = sharedState;
SelectedTheme = AvailableThemes[1];
SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1];
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;
settingsManager.RelayPropertyFor(this,
vm => vm.SelectedTheme,
settings => settings.Theme);
}
partial void OnSelectedThemeChanged(string? value)
@ -134,6 +143,112 @@ public partial class SettingsViewModel : PageViewModelBase
await dialog.ShowAsync();
}
#region System
/// <summary>
/// Adds Stability Matrix to Start Menu for the current user.
/// </summary>
[RelayCommand]
private async Task AddToStartMenu()
{
if (!Compat.IsWindows)
{
notificationService.Show(
"Not supported", "This feature is only supported on Windows.");
return;
}
await using var _ = new MinimumDelay(200, 300);
var shortcutDir = new DirectoryPath(
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
"Programs");
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk");
var appPath = Compat.AppCurrentPath;
var iconPath = shortcutDir.JoinFile("Stability Matrix.ico");
await Assets.AppIcon.ExtractTo(iconPath);
WindowsShortcuts.CreateShortcut(
shortcutLink, appPath, iconPath, "Stability Matrix");
notificationService.Show("Added to Start Menu",
"Stability Matrix has been added to the Start Menu.", NotificationType.Success);
}
/// <summary>
/// Add Stability Matrix to Start Menu for all users.
/// <remarks>Requires Admin elevation.</remarks>
/// </summary>
[RelayCommand]
private async Task AddToGlobalStartMenu()
{
if (!Compat.IsWindows)
{
notificationService.Show(
"Not supported", "This feature is only supported on Windows.");
return;
}
// Confirmation dialog
var dialog = new BetterContentDialog
{
Title = "This will create a shortcut for Stability Matrix in the Start Menu for all users",
Content = "You will be prompted for administrator privileges. Continue?",
PrimaryButtonText = "Yes",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
if (await dialog.ShowAsync() != ContentDialogResult.Primary)
{
return;
}
await using var _ = new MinimumDelay(200, 300);
var shortcutDir = new DirectoryPath(
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
"Programs");
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk");
var appPath = Compat.AppCurrentPath;
var iconPath = shortcutDir.JoinFile("Stability Matrix.ico");
// We can't directly write to the targets, so extract to temporary directory first
using var tempDir = new TempDirectoryPath();
await Assets.AppIcon.ExtractTo(tempDir.JoinFile("Stability Matrix.ico"));
WindowsShortcuts.CreateShortcut(
tempDir.JoinFile("Stability Matrix.lnk"), appPath, iconPath,
"Stability Matrix");
// Move to target
try
{
var moveLinkResult = await WindowsElevated.MoveFiles(
(tempDir.JoinFile("Stability Matrix.lnk"), shortcutLink),
(tempDir.JoinFile("Stability Matrix.ico"), iconPath));
if (moveLinkResult != 0)
{
notificationService.ShowPersistent("Failed to create shortcut", $"Could not copy shortcut",
NotificationType.Error);
}
}
catch (Win32Exception e)
{
// We'll get this exception if user cancels UAC
Logger.Warn(e, "Could not create shortcut");
notificationService.Show("Could not create shortcut", "", NotificationType.Warning);
return;
}
notificationService.Show("Added to Start Menu",
"Stability Matrix has been added to the Start Menu for all users.", NotificationType.Success);
}
#endregion
#region Debug Section
public void LoadDebugInfo()
{

40
StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml

@ -13,15 +13,6 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="StabilityMatrix.Avalonia.Views.CheckpointsPage">
<controls:UserControlBase.DataTemplates>
<!-- Template for dropdown category checkbox item -->
<DataTemplate DataType="{x:Type vm:CheckpointFolder}">
<ui:ToggleMenuFlyoutItem
Text="{Binding TitleWithFilesCount}"
IsChecked="{Binding IsCategoryEnabled, Mode=TwoWay}"/>
</DataTemplate>
</controls:UserControlBase.DataTemplates>
<controls:UserControlBase.Resources>
<!--Direction="0"
ShadowDepth="0"-->
@ -392,30 +383,17 @@
HorizontalAlignment="Right">
<DropDownButton.Flyout>
<ui:FAMenuFlyout
ItemsSource="{Binding DisplayedCheckpointFolders}"/>
ItemsSource="{Binding CheckpointFolders}">
<ui:FAMenuFlyout.ItemTemplate>
<DataTemplate DataType="{x:Type vm:CheckpointFolder}">
<ui:ToggleMenuFlyoutItem
Text="{Binding TitleWithFilesCount}"
IsChecked="{Binding IsCategoryEnabled, Mode=TwoWay}"/>
</DataTemplate>
</ui:FAMenuFlyout.ItemTemplate>
</ui:FAMenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
<!--<ComboBox
VerticalAlignment="Center"
HorizontalAlignment="Right"
ItemsSource="{Binding CheckpointFolders}"
MinWidth="180"
SelectedIndex="0">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Template" Value="{StaticResource CategoryDropDownStyle}" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type vm:CheckpointFolder}">
<StackPanel Margin="10,0,0,0" VerticalAlignment="Top">
<TextBlock Margin="0,5,0,5" Text="Categories" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>-->
</StackPanel>
<!-- Main view with model cards -->

21
StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs

@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
using AvaloniaEdit;
using AvaloniaEdit.TextMate;
@ -19,15 +20,23 @@ public partial class LaunchPageView : UserControlBase
{
InitializeComponent();
var editor = this.FindControl<TextEditor>("Console");
var options = new RegistryOptions(ThemeName.HighContrastLight);
if (editor is not null)
{
var options = new RegistryOptions(ThemeName.DarkPlus);
// Config hyperlinks
editor.TextArea.Options.EnableHyperlinks = true;
editor.TextArea.Options.RequireControlModifierForHyperlinkClick = false;
editor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Coral;
var textMate = editor.InstallTextMate(options);
var scope = options.GetScopeByLanguageId("log");
var textMate = editor.InstallTextMate(options);
var scope = options.GetScopeByLanguageId("log");
if (scope is null) throw new InvalidOperationException("Scope is null");
if (scope is null) throw new InvalidOperationException("Scope is null");
textMate.SetGrammar(scope);
textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus));
textMate.SetGrammar(scope);
textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus));
}
}
protected override void OnUnloaded(RoutedEventArgs e)

154
StabilityMatrix.Avalonia/Views/SettingsPage.axaml

@ -15,75 +15,131 @@
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<StackPanel Spacing="4">
<ui:SettingsExpander Header="Theme" Margin="8"
IconSource="WeatherMoon">
<ui:SettingsExpander.Footer>
<ComboBox
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme}"
Margin="8"
MinWidth="100"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- TODO: Text2Image host port settings -->
<!-- Checkpoints Manager Options -->
<StackPanel Spacing="2" Margin="12,16">
<!-- Theme -->
<Grid RowDefinitions="auto,*">
<TextBlock
FontWeight="Medium"
Text="Checkpoint Manager"
Margin="8,16,0,0" />
Text="Appearance"
Margin="0,0,0,8" />
<StackPanel Grid.Row="1">
<ui:SettingsExpander
IconSource="Folder"
Header="Remove shared checkpoints directory symbolic links on shutdown"
Description="Select this option if you're having problems moving Stability Matrix to another drive."
Margin="8">
<ui:SettingsExpander
Header="Theme"
IconSource="WeatherMoon"
Margin="8,0">
<ui:SettingsExpander.Footer>
<CheckBox Margin="8"
IsChecked="{Binding RemoveSymlinksOnShutdown}"/>
<ComboBox
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme}"
Margin="8"
MinWidth="100"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</Grid>
<!-- TODO: Text2Image host port settings -->
<!-- Checkpoints Manager Options -->
<Grid RowDefinitions="auto,*">
<TextBlock
FontWeight="Medium"
Text="Checkpoint Manager"
Margin="0,16,0,8" />
<ui:SettingsExpander
Grid.Row="1"
IconSource="Folder"
Header="Remove shared checkpoints directory symbolic links on shutdown"
Description="Select this option if you're having problems moving Stability Matrix to another drive"
Margin="8,0">
<ui:SettingsExpander.Footer>
<CheckBox Margin="8"
IsChecked="{Binding RemoveSymlinksOnShutdown}"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Grid>
<!-- Python Options -->
<Grid RowDefinitions="auto,*">
<TextBlock
FontWeight="Medium"
Text="Python Environment"
Margin="8,16,0,0" />
<StackPanel Grid.Row="1">
<ui:SettingsExpander
Header="Embedded Python Environment"
Margin="8">
<ui:SettingsExpander.IconSource>
<controls:FASymbolIconSource Symbol="fa-brands fa-python"/>
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="16">
<controls:ProgressRing
IsEnabled="{Binding CheckPythonVersionCommand.IsRunning}"
IsVisible="{Binding CheckPythonVersionCommand.IsRunning}"
IsIndeterminate="True"
BorderThickness="3"/>
<Button Content="Check Version" Command="{Binding CheckPythonVersionCommand}"/>
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
Margin="0,16,0,0" />
<ui:SettingsExpander
Grid.Row="1"
Header="Embedded Python Environment"
Margin="8">
<ui:SettingsExpander.IconSource>
<controls:FASymbolIconSource Symbol="fa-brands fa-python"/>
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="16">
<controls:ProgressRing
IsEnabled="{Binding CheckPythonVersionCommand.IsRunning}"
IsVisible="{Binding CheckPythonVersionCommand.IsRunning}"
IsIndeterminate="True"
BorderThickness="3"/>
<Button Content="Check Version" Command="{Binding CheckPythonVersionCommand}"/>
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Grid>
<!-- System Options -->
<Grid RowDefinitions="auto,*">
<TextBlock
FontWeight="Medium"
Text="System"
Margin="0,16,0,0" />
<ui:SettingsExpander
Grid.Row="1"
ToolTip.Tip="{OnPlatform Default='Only available on Windows', Windows={x:Null}}"
Header="Add Stability Matrix to the Start Menu"
Description="Uses the current app location, you can run this again if you move the app"
IconSource="StarAdd"
Margin="8">
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<controls:ProgressRing
IsIndeterminate="True"
IsEnabled="{Binding IsVisible, RelativeSource={RelativeSource Self}}"
BorderThickness="3">
<controls:ProgressRing.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.Or}">
<Binding Path="AddToStartMenuCommand.IsRunning"/>
<Binding Path="AddToGlobalStartMenuCommand.IsRunning"/>
</MultiBinding>
</controls:ProgressRing.IsVisible>
</controls:ProgressRing>
<SplitButton
Command="{Binding AddToStartMenuCommand}"
IsEnabled="{OnPlatform Default=False, Windows=True}"
Content="Add for Current User">
<SplitButton.Flyout>
<ui:FAMenuFlyout Placement="Bottom">
<ui:MenuFlyoutItem
Command="{Binding AddToGlobalStartMenuCommand}"
IconSource="Admin"
Text="Add for All Users"/>
</ui:FAMenuFlyout>
</SplitButton.Flyout>
</SplitButton>
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Grid>
<!-- Debug Options -->
<Grid RowDefinitions="auto,*" IsVisible="{Binding SharedState.IsDebugMode}">
<Grid RowDefinitions="auto,*"
Margin="0,4,0,0"
IsVisible="{Binding SharedState.IsDebugMode}" >
<TextBlock
FontWeight="Medium"
Text="Debug Options"
Margin="8,16,0,0" />
<StackPanel Grid.Row="1">
Margin="0,16,0,0" />
<ui:SettingsExpander
Grid.Row="1"
IconSource="Code"
Command="{Binding LoadDebugInfo}"
Header="Debug Options"
@ -132,7 +188,6 @@
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</Grid>
<!-- TODO: Python card -->
@ -141,9 +196,8 @@
<!-- TODO: Directories card -->
<Grid RowDefinitions="auto,*">
<Grid RowDefinitions="auto,*" Margin="0,4,0,0">
<StackPanel
Margin="8,0,0,0"
Grid.Row="1"
HorizontalAlignment="Left"
Orientation="Vertical">

11
StabilityMatrix.Core/Helper/Compat.cs

@ -4,6 +4,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Semver;
using Sentry.Protocol;
using StabilityMatrix.Core.Models.FileInterfaces;
namespace StabilityMatrix.Core.Helper;
@ -52,6 +53,11 @@ public static class Compat
/// Current directory the app is in.
/// </summary>
public static DirectoryPath AppCurrentDir { get; }
/// <summary>
/// Current path to the app.
/// </summary>
public static FilePath AppCurrentPath => AppCurrentDir.JoinFile(GetExecutableName());
// File extensions
/// <summary>
@ -95,9 +101,10 @@ public static class Compat
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Platform = PlatformKind.Linux | PlatformKind.Unix;
// We need to get application path using `$APPIMAGE`, then get the directory name
// For AppImage builds, the path is in `$APPIMAGE`
var appPath = Environment.GetEnvironmentVariable("APPIMAGE") ??
throw new Exception("Could not find application path");
AppContext.BaseDirectory;
AppCurrentDir = Path.GetDirectoryName(appPath) ??
throw new Exception("Could not find application directory");
ExeExtension = "";

15
StabilityMatrix.Core/Models/FileInterfaces/TempDirectoryPath.cs

@ -0,0 +1,15 @@
namespace StabilityMatrix.Core.Models.FileInterfaces;
public class TempDirectoryPath : DirectoryPath, IDisposable
{
public TempDirectoryPath() : base(Path.GetTempPath(), Path.GetRandomFileName())
{
Directory.CreateDirectory(FullPath);
}
public void Dispose()
{
Directory.Delete(FullPath, true);
GC.SuppressFinalize(this);
}
}
Loading…
Cancel
Save