Ionite
1 year ago
11 changed files with 574 additions and 4 deletions
@ -0,0 +1,39 @@
|
||||
<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:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||
xmlns:ui="using:FluentAvalonia.UI.Controls" |
||||
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
d:DataContext="{x:Static mocks:DesignData.RefreshBadgeViewModel}" |
||||
x:DataType="vm:RefreshBadgeViewModel" |
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
x:Class="StabilityMatrix.Avalonia.Controls.RefreshBadge"> |
||||
<Grid> |
||||
<TextBlock Text="Test"></TextBlock> |
||||
<Button |
||||
BorderThickness="0" |
||||
Command="{Binding RefreshCommand}" |
||||
FontSize="26" |
||||
Foreground="{Binding ColorBrush}" |
||||
Margin="4" |
||||
Padding="2" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
IsEnabled="{Binding !IsWorking}"> |
||||
<ui:SymbolIcon Symbol="{Binding Icon}"/> |
||||
</Button> |
||||
<controls:ProgressRing |
||||
FontSize="14" |
||||
Grid.Row="0" |
||||
Height="20" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
IsEnabled="{Binding IsWorking}" |
||||
IsIndeterminate="True" |
||||
IsVisible="{Binding IsWorking}" |
||||
Width="20" |
||||
ToolTip.Tip="{Binding CurrentToolTip}" /> |
||||
</Grid> |
||||
</controls:UserControlBase> |
@ -0,0 +1,16 @@
|
||||
using Avalonia.Markup.Xaml; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Controls; |
||||
|
||||
public partial class RefreshBadge : UserControlBase |
||||
{ |
||||
public RefreshBadge() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
private void InitializeComponent() |
||||
{ |
||||
AvaloniaXamlLoader.Load(this); |
||||
} |
||||
} |
@ -0,0 +1,10 @@
|
||||
using Avalonia.Media; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Styles; |
||||
|
||||
public static class ThemeColors |
||||
{ |
||||
public static readonly SolidColorBrush ThemeGreen = SolidColorBrush.Parse("#4caf50"); |
||||
public static readonly SolidColorBrush ThemeRed = SolidColorBrush.Parse("#f44336"); |
||||
public static readonly SolidColorBrush ThemeYellow = SolidColorBrush.Parse("#ffeb3b"); |
||||
} |
@ -0,0 +1,156 @@
|
||||
using System; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Text.Json; |
||||
using System.Text.Json.Serialization; |
||||
using System.Threading.Tasks; |
||||
using AsyncAwaitBestPractices; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Platform.Storage; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using NLog; |
||||
using StabilityMatrix.Avalonia.Views.Dialogs; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Helper; |
||||
using StabilityMatrix.Core.Models.Progress; |
||||
using StabilityMatrix.Core.Models.Settings; |
||||
using StabilityMatrix.Core.Services; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||
|
||||
[View(typeof(SelectDataDirectoryDialog))] |
||||
public partial class SelectDataDirectoryViewModel : ViewModelBase |
||||
{ |
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
||||
private static string DefaultInstallLocation => Compat.AppDataHome; |
||||
|
||||
private readonly ISettingsManager settingsManager; |
||||
|
||||
private const string ValidExistingDirectoryText = "Valid existing data directory found"; |
||||
private const string InvalidDirectoryText = |
||||
"Directory must be empty or have a valid settings.json file"; |
||||
private const string NotEnoughFreeSpaceText = "Not enough free space on the selected drive"; |
||||
|
||||
[ObservableProperty] private string dataDirectory = DefaultInstallLocation; |
||||
[ObservableProperty] private bool isPortableMode; |
||||
|
||||
[ObservableProperty] private string directoryStatusText = string.Empty; |
||||
[ObservableProperty] private bool isStatusBadgeVisible; |
||||
[ObservableProperty] private bool isDirectoryValid; |
||||
|
||||
public RefreshBadgeViewModel ValidatorRefreshBadge { get; } = new() |
||||
{ |
||||
State = ProgressState.Inactive, |
||||
SuccessToolTipText = ValidExistingDirectoryText, |
||||
FailToolTipText = InvalidDirectoryText |
||||
}; |
||||
|
||||
public bool HasOldData => settingsManager.GetOldInstalledPackages().Any(); |
||||
|
||||
public SelectDataDirectoryViewModel(ISettingsManager settingsManager) |
||||
{ |
||||
this.settingsManager = settingsManager; |
||||
ValidatorRefreshBadge.RefreshFunc = ValidateDataDirectory; |
||||
} |
||||
|
||||
public override void OnLoaded() |
||||
{ |
||||
ValidatorRefreshBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget(); |
||||
} |
||||
|
||||
// Revalidate on data directory change |
||||
partial void OnDataDirectoryChanged(string value) |
||||
{ |
||||
ValidatorRefreshBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget(); |
||||
} |
||||
|
||||
private async Task<bool> ValidateDataDirectory() |
||||
{ |
||||
await using var delay = new MinimumDelay(100, 200); |
||||
|
||||
// Doesn't exist, this is fine as a new install, hide badge |
||||
if (!Directory.Exists(DataDirectory)) |
||||
{ |
||||
IsStatusBadgeVisible = false; |
||||
IsDirectoryValid = true; |
||||
return true; |
||||
} |
||||
// Otherwise check that a settings.json exists |
||||
var settingsPath = Path.Combine(DataDirectory, "settings.json"); |
||||
|
||||
// settings.json exists: Try deserializing it |
||||
if (File.Exists(settingsPath)) |
||||
{ |
||||
try |
||||
{ |
||||
var jsonText = await File.ReadAllTextAsync(settingsPath); |
||||
var _ = JsonSerializer.Deserialize<Settings>(jsonText, new JsonSerializerOptions |
||||
{ |
||||
Converters = { new JsonStringEnumConverter() } |
||||
}); |
||||
// If successful, show existing badge |
||||
IsStatusBadgeVisible = true; |
||||
IsDirectoryValid = true; |
||||
DirectoryStatusText = ValidExistingDirectoryText; |
||||
return true; |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
Logger.Info("Failed to deserialize settings.json: {Msg}", e.Message); |
||||
// If not, show error badge, and set directory to invalid to prevent continuing |
||||
IsStatusBadgeVisible = true; |
||||
IsDirectoryValid = false; |
||||
DirectoryStatusText = InvalidDirectoryText; |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// No settings.json |
||||
|
||||
// Check if the directory is %APPDATA%\StabilityMatrix: hide badge and set directory valid |
||||
if (DataDirectory == DefaultInstallLocation) |
||||
{ |
||||
IsStatusBadgeVisible = false; |
||||
IsDirectoryValid = true; |
||||
return true; |
||||
} |
||||
|
||||
// Check if the directory is empty: hide badge and set directory to valid |
||||
var isEmpty = !Directory.EnumerateFileSystemEntries(DataDirectory).Any(); |
||||
if (isEmpty) |
||||
{ |
||||
IsStatusBadgeVisible = false; |
||||
IsDirectoryValid = true; |
||||
return true; |
||||
} |
||||
|
||||
// Not empty and not appdata: show error badge, and set directory to invalid |
||||
IsStatusBadgeVisible = true; |
||||
IsDirectoryValid = false; |
||||
DirectoryStatusText = InvalidDirectoryText; |
||||
return false; |
||||
} |
||||
|
||||
private bool CanPickFolder => App.StorageProvider.CanPickFolder; |
||||
|
||||
[RelayCommand(CanExecute = nameof(CanPickFolder))] |
||||
private async Task ShowFolderBrowserDialog() |
||||
{ |
||||
var provider = App.StorageProvider; |
||||
var result = await provider.OpenFolderPickerAsync(new FolderPickerOpenOptions |
||||
{ |
||||
Title = "Select Data Folder", |
||||
AllowMultiple = false |
||||
}); |
||||
|
||||
if (result.Count != 1) return; |
||||
|
||||
DataDirectory = result[0].Path.LocalPath; |
||||
} |
||||
|
||||
partial void OnIsPortableModeChanged(bool value) |
||||
{ |
||||
DataDirectory = value ? Compat.AppCurrentDir + "Data" : DefaultInstallLocation; |
||||
} |
||||
} |
@ -0,0 +1,96 @@
|
||||
using System; |
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.Threading.Tasks; |
||||
using Avalonia.Media; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using FluentAvalonia.UI.Controls; |
||||
using NLog; |
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Avalonia.Styles; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Models.Progress; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels; |
||||
|
||||
[View(typeof(RefreshBadge))] |
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
||||
public partial class RefreshBadgeViewModel : ViewModelBase |
||||
{ |
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
||||
|
||||
public string WorkingToolTipText { get; set; } = "Loading..."; |
||||
public string SuccessToolTipText { get; set; } = "Success"; |
||||
public string InactiveToolTipText { get; set; } = ""; |
||||
public string FailToolTipText { get; set; } = "Failed"; |
||||
|
||||
public Symbol InactiveIcon { get; set; } = Symbol.Clear; |
||||
public Symbol SuccessIcon { get; set; } = Symbol.Checkmark; |
||||
public Symbol FailIcon { get; set; } = Symbol.AlertUrgent; |
||||
|
||||
public IBrush SuccessColorBrush { get; set; } = ThemeColors.ThemeGreen; |
||||
public IBrush InactiveColorBrush { get; set; } = ThemeColors.ThemeYellow; |
||||
public IBrush FailColorBrush { get; set; } = ThemeColors.ThemeYellow; |
||||
|
||||
public Func<Task<bool>>? RefreshFunc { get; set; } |
||||
|
||||
[ObservableProperty] |
||||
[NotifyPropertyChangedFor(nameof(IsWorking))] |
||||
[NotifyPropertyChangedFor(nameof(ColorBrush))] |
||||
[NotifyPropertyChangedFor(nameof(CurrentToolTip))] |
||||
[NotifyPropertyChangedFor(nameof(Icon))] |
||||
private ProgressState state; |
||||
|
||||
public bool IsWorking => State == ProgressState.Working; |
||||
|
||||
/*public ControlAppearance Appearance => State switch |
||||
{ |
||||
ProgressState.Working => ControlAppearance.Info, |
||||
ProgressState.Success => ControlAppearance.Success, |
||||
ProgressState.Failed => ControlAppearance.Danger, |
||||
_ => ControlAppearance.Secondary |
||||
};*/ |
||||
|
||||
public IBrush ColorBrush => State switch |
||||
{ |
||||
ProgressState.Success => SuccessColorBrush, |
||||
ProgressState.Inactive => InactiveColorBrush, |
||||
ProgressState.Failed => FailColorBrush, |
||||
_ => Brushes.Gray |
||||
}; |
||||
|
||||
public string CurrentToolTip => State switch |
||||
{ |
||||
ProgressState.Working => WorkingToolTipText, |
||||
ProgressState.Success => SuccessToolTipText, |
||||
ProgressState.Inactive => InactiveToolTipText, |
||||
ProgressState.Failed => FailToolTipText, |
||||
_ => "" |
||||
}; |
||||
|
||||
public Symbol Icon => State switch |
||||
{ |
||||
ProgressState.Success => SuccessIcon, |
||||
ProgressState.Failed => FailIcon, |
||||
_ => InactiveIcon |
||||
}; |
||||
|
||||
[RelayCommand] |
||||
private async Task Refresh() |
||||
{ |
||||
Logger.Info("Running refresh command..."); |
||||
if (RefreshFunc == null) return; |
||||
|
||||
State = ProgressState.Working; |
||||
try |
||||
{ |
||||
var result = await RefreshFunc.Invoke(); |
||||
State = result ? ProgressState.Success : ProgressState.Failed; |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
State = ProgressState.Failed; |
||||
Logger.Error(ex, "Refresh command failed: {Ex}", ex.Message); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,100 @@
|
||||
<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:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs" |
||||
xmlns:ui="using:FluentAvalonia.UI.Controls" |
||||
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime" |
||||
d:DataContext="{x:Static mocks:DesignData.SelectDataDirectoryViewModel}" |
||||
x:DataType="dialogs:SelectDataDirectoryViewModel" |
||||
x:CompileBindings="True" |
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.SelectDataDirectoryDialog"> |
||||
|
||||
<Grid RowDefinitions="Auto,1*,1*" |
||||
Margin="16,0,16,16" |
||||
MinHeight="450" |
||||
MaxWidth="700" > |
||||
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,16"> |
||||
<ui:InfoBar |
||||
IsClosable="False" |
||||
IsOpen="True" |
||||
Margin="0,0,0,16" |
||||
IsVisible="{Binding HasOldData}" |
||||
Title="Welcome back! In this update, you can optionally choose a custom location to store all data. If you choose a new location, or opt to use Portable Mode, you'll be able to migrate your existing data on the next page."/> |
||||
|
||||
<Label |
||||
Content="Data Directory" |
||||
FontSize="13" |
||||
Margin="0,16,0,0" /> |
||||
<Grid ColumnDefinitions="*,Auto"> |
||||
|
||||
<TextBox |
||||
Height="36" |
||||
IsEnabled="{Binding !IsPortableMode}" |
||||
Margin="0,0,8,0" |
||||
Watermark="{Binding DefaultInstallLocation}" |
||||
Text="{Binding DataDirectory}" |
||||
VerticalAlignment="Stretch" /> |
||||
|
||||
<Button |
||||
Command="{Binding ShowFolderBrowserDialogCommand}" |
||||
Grid.Column="1" |
||||
Height="36" |
||||
HorizontalAlignment="Stretch" |
||||
IsEnabled="{Binding !IsPortableMode}"> |
||||
<ui:SymbolIcon Symbol="OpenFolder" /> |
||||
</Button> |
||||
</Grid> |
||||
|
||||
<TextBlock |
||||
Text="This is where the model checkpoints, LORAs, web UIs, settings, etc. will be installed. If you were satisfied with the previous versions, you don't need to change anything here." |
||||
TextWrapping="Wrap" |
||||
Foreground="LightGray" |
||||
FontSize="12" |
||||
Margin="0,8,0,0" /> |
||||
|
||||
<CheckBox |
||||
Content="Portable Mode" |
||||
IsChecked="{Binding IsPortableMode, Mode=TwoWay}" |
||||
Margin="0,16,0,0" /> |
||||
|
||||
<ui:InfoBar |
||||
IsClosable="False" |
||||
IsOpen="True" |
||||
FontSize="13" |
||||
Margin="0,8,0,0" |
||||
Padding="16" |
||||
Title="In Portable Mode, all data and settings will be stored in the same directory as the application. You will be able to move the application with its 'Data' folder to a different location or computer." /> |
||||
|
||||
</StackPanel> |
||||
|
||||
<!-- Indicator of existing or new data directory --> |
||||
<StackPanel |
||||
Grid.Row="1" |
||||
HorizontalAlignment="Center" |
||||
Margin="8,0,8,8" |
||||
Orientation="Horizontal" |
||||
IsVisible="{Binding IsStatusBadgeVisible}"> |
||||
<controls:RefreshBadge DataContext="{Binding ValidatorRefreshBadge}" /> |
||||
<TextBlock |
||||
FontSize="14" |
||||
Text="{Binding DirectoryStatusText}" |
||||
VerticalAlignment="Center" /> |
||||
</StackPanel> |
||||
|
||||
<!-- Appearance="Success" |
||||
Click="ContinueButton_OnClick" --> |
||||
<Button |
||||
Content="Continue" |
||||
FontSize="16" |
||||
Grid.Row="2" |
||||
HorizontalAlignment="Center" |
||||
IsEnabled="{Binding IsDirectoryValid}" |
||||
Padding="16,8" /> |
||||
</Grid> |
||||
|
||||
</controls:UserControlBase> |
@ -0,0 +1,17 @@
|
||||
using Avalonia.Markup.Xaml; |
||||
using StabilityMatrix.Avalonia.Controls; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views.Dialogs; |
||||
|
||||
public partial class SelectDataDirectoryDialog : UserControlBase |
||||
{ |
||||
public SelectDataDirectoryDialog() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
private void InitializeComponent() |
||||
{ |
||||
AvaloniaXamlLoader.Load(this); |
||||
} |
||||
} |
Loading…
Reference in new issue