diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae9cc140..c5bb7927 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - name: Set up .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44a9426f..1b1e23ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,10 +82,10 @@ jobs: sudo apt-get -y install libfuse2 dotnet tool install --framework net6.0 -g KuiperZone.PupNet - - name: Set up .NET 7 + - name: Set up .NET 8 uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: PupNet Build env: @@ -143,10 +143,10 @@ jobs: echo "Using version ${{ github.event.inputs.version }}" echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $env:GITHUB_ENV - - name: Set up .NET 7 + - name: Set up .NET 8 uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install dependencies run: dotnet restore @@ -197,8 +197,8 @@ jobs: # Zip each build - name: Zip Artifacts run: | - zip -r StabilityMatrix-win-x64.zip StabilityMatrix-win-x64/* - zip -r StabilityMatrix-linux-x64.zip StabilityMatrix-linux-x64/* + cd StabilityMatrix-win-x64 && zip -r ../StabilityMatrix-win-x64.zip ./. && cd $OLDPWD + cd StabilityMatrix-linux-x64 && zip -r ../StabilityMatrix-linux-x64.zip ./. && cd $OLDPWD - name: Create Github Release id: create_release @@ -235,10 +235,16 @@ jobs: python-version: '3.11' - name: Install Python Dependencies - run: pip install stability-matrix-tools~=0.2.7 + run: pip install stability-matrix-tools>=0.2.10 --upgrade - name: Publish Auto-Update Release - run: sm-tools updates publish-matrix -v $RELEASE_VERSION -y + env: + SM_B2_API_ID: ${{ secrets.SM_B2_API_ID }} + SM_B2_API_KEY: ${{ secrets.SM_B2_API_KEY }} + SM_CF_CACHE_PURGE_TOKEN: ${{ secrets.SM_CF_CACHE_PURGE_TOKEN }} + SM_CF_ZONE_ID: ${{ secrets.SM_CF_ZONE_ID }} + SM_SIGNING_PRIVATE_KEY: ${{ secrets.SM_SIGNING_PRIVATE_KEY }} + run: sm-tools updates publish-matrix -v ${{ github.event.inputs.version }} -y publish-auto-update-b2: name: Publish Auto-Update Release (B2) @@ -253,23 +259,22 @@ jobs: echo "Using version ${{ github.event.inputs.version }}" echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $env:GITHUB_ENV + # Downloads all previous artifacts to the current working directory + - name: Download Artifacts + uses: actions/download-artifact@v3 + + # Zip each build + - name: Zip Artifacts + run: | + cd StabilityMatrix-win-x64 && zip -r ../StabilityMatrix-win-x64.zip ./. && cd $OLDPWD + cd StabilityMatrix-linux-x64 && zip -r ../StabilityMatrix-linux-x64.zip ./. && cd $OLDPWD + - uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install Python Dependencies - run: pip install stability-matrix-tools~=0.2.7 - - - name: Download Changelog - run: > - sm-tools updates download-changelog -v $RELEASE_VERSION -y - --changelog - - # Zip each build - - name: Zip Artifacts - run: | - zip -r StabilityMatrix-win-x64.zip StabilityMatrix-win-x64/* - zip -r StabilityMatrix-linux-x64.zip StabilityMatrix-linux-x64/* + run: pip install stability-matrix-tools>=0.2.10 --upgrade # Check that the zips and CHANGELOG.md are in the current working directory - name: Check files @@ -288,10 +293,16 @@ jobs: fi - name: Publish Auto-Update Release + env: + SM_B2_API_ID: ${{ secrets.SM_B2_API_ID }} + SM_B2_API_KEY: ${{ secrets.SM_B2_API_KEY }} + SM_CF_CACHE_PURGE_TOKEN: ${{ secrets.SM_CF_CACHE_PURGE_TOKEN }} + SM_CF_ZONE_ID: ${{ secrets.SM_CF_ZONE_ID }} + SM_SIGNING_PRIVATE_KEY: ${{ secrets.SM_SIGNING_PRIVATE_KEY }} run: > - sm-tools updates publish-files-v3 -v $RELEASE_VERSION -y + sm-tools updates publish-files-v3 -v ${{ github.event.inputs.version }} --channel ${{ github.event.inputs.auto-update-release-channel }} --changelog CHANGELOG.md --win-x64 StabilityMatrix-win-x64.zip --linux-x64 StabilityMatrix-linux-x64.zip - --b2-bucket-name ${{ secrets.B2_BUCKET_NAME }} + -y diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml index 244472bb..a0720e5d 100644 --- a/.github/workflows/test-ui.yml +++ b/.github/workflows/test-ui.yml @@ -18,7 +18,7 @@ jobs: - name: Set up .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 68103040..1b623413 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v2 with: - dotnet-version: 6.0.x + dotnet-version: '8.0.x' - name: Bump versions uses: SiqiLu/dotnet-bump-version@2.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index d2aa402d..5caace0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,44 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +## v2.7.0-dev.2 +### Added +#### General +- Added an X button to all search fields to instantly clear them (Esc key also works) +#### Outputs Page +- Added Refresh button to update gallery from file system changes +#### Checkpoints Page +- Added the ability to drag & drop checkpoints between different folders +## Changed +#### Outputs Page +- Updated button and menu layout +#### Packages Page +- Rearranged Add Package dialog slightly to accommodate longer package list +### Fixed +- Fixed InvalidOperation errors when signing into accounts shortly after signing out, while the previous account update is still running +- Fixed Outputs page reverting back to Shared Output Folder every time the page is reloaded +- Potentially fixed updates sometimes clearing settings or launching in the wrong directory + +## v2.7.0-dev.1 +### Added +- Accounts Settings Subpage + - Lykos Account sign-up and login - currently for Patreon OAuth connections but GitHub requests caching and settings sync are planned + - Supporters can now connect your Patreon accounts, then head to the Updates page to choose to receive auto-updates from the Dev or Preview channels + - CivitAI Account login with API key - enables downloading models from the Browser page that require CivitAI logins, more integrations like liking and commenting are also planned +- Updates Settings Subpage + - Toggle auto-update notifications and manually check for updates + - Choose between Stable, Preview, and Dev update channels +## Changed +- Model Browser page has been redesigned, featuring more information like rating and download counts + ## v2.6.3 ### Fixed - Fixed InvalidOperationException during prerequisite installs on certain platforms where process name and duration reporting are not supported +## v2.7.0-dev.3 +## Changed +#### Model Browser +- Improved number formatting with K/M suffixes for download and favorite counts +- Animated zoom effect on hovering over model images ## v2.6.2 ### Changed diff --git a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj index 75091167..eb84e3f3 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj +++ b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 win-x64;linux-x64;osx-x64;osx-arm64 enable enable diff --git a/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs b/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs index 3a729ab2..183538a1 100644 --- a/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs +++ b/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs @@ -1,22 +1,25 @@ using System; using System.Threading; +using System.Threading.Tasks; using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Media; using Avalonia.Styling; using FluentAvalonia.UI.Media.Animation; +using Projektanker.Icons.Avalonia; namespace StabilityMatrix.Avalonia.Animations; public class BetterSlideNavigationTransition : BaseTransitionInfo { public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(167); - + /// /// Gets or sets the type of animation effect to play during the slide transition. /// - public SlideNavigationTransitionEffect Effect { get; set; } = SlideNavigationTransitionEffect.FromRight; + public SlideNavigationTransitionEffect Effect { get; set; } = + SlideNavigationTransitionEffect.FromRight; /// /// Gets or sets the HorizontalOffset used when animating from the Left or Right @@ -27,7 +30,12 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo /// Gets or sets the VerticalOffset used when animating from the Top or Bottom /// public double FromVerticalOffset { get; set; } = 56; - + + /// + /// Gets or sets the easing function applied to the slide transition. + /// + public Easing Easing { get; set; } = new SplineEasing(0.1, 0.9, 0.2, 1.0); + public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken) { double length = 0; @@ -52,24 +60,26 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo var animation = new Animation { - Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0), + Easing = Easing, Children = { new KeyFrame { Setters = { - new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, length), + new Setter( + isVertical + ? TranslateTransform.YProperty + : TranslateTransform.XProperty, + length + ), new Setter(Visual.OpacityProperty, 0d) }, Cue = new Cue(0d) }, new KeyFrame { - Setters= - { - new Setter(Visual.OpacityProperty, 1d) - }, + Setters = { new Setter(Visual.OpacityProperty, 1d) }, Cue = new Cue(0.05d) }, new KeyFrame @@ -77,7 +87,12 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo Setters = { new Setter(Visual.OpacityProperty, 1d), - new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, 0.0) + new Setter( + isVertical + ? TranslateTransform.YProperty + : TranslateTransform.XProperty, + 0.0 + ) }, Cue = new Cue(1d) } @@ -93,4 +108,22 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo visual.Opacity = 1; } } + + public static BetterSlideNavigationTransition PageSlideFromLeft => + new() + { + Duration = TimeSpan.FromMilliseconds(300), + Effect = SlideNavigationTransitionEffect.FromLeft, + FromHorizontalOffset = 150, + Easing = new SplineEasing(0.6, 0.4, 0.1, 0.1) + }; + + public static BetterSlideNavigationTransition PageSlideFromRight => + new() + { + Duration = TimeSpan.FromMilliseconds(300), + Effect = SlideNavigationTransitionEffect.FromRight, + FromHorizontalOffset = 150, + Easing = new SplineEasing(0.6, 0.4, 0.1, 0.1) + }; } diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index f33a4d5c..2ed2726d 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -22,11 +22,15 @@ + + 700 + 32 + avares://StabilityMatrix.Avalonia/Assets/Fonts/NotoSansJP#Noto Sans JP @@ -39,6 +43,7 @@ + @@ -62,6 +67,8 @@ + + + diff --git a/StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml.cs b/StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml.cs new file mode 100644 index 00000000..0f770a96 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Metadata; +using Avalonia.VisualTree; +using FluentAvalonia.UI.Controls; +using StabilityMatrix.Core.Processes; + +namespace StabilityMatrix.Avalonia.Controls; + +public class SettingsAccountLinkExpander : TemplatedControl +{ + private readonly List _items = new(); + + [Content] + public List Items => _items; + + // ReSharper disable MemberCanBePrivate.Global + public static readonly StyledProperty HeaderProperty = + HeaderedItemsControl.HeaderProperty.AddOwner(); + + public object? Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + public static readonly StyledProperty HeaderTargetUriProperty = AvaloniaProperty.Register< + SettingsAccountLinkExpander, + Uri? + >("HeaderTargetUri"); + + public Uri? HeaderTargetUri + { + get => GetValue(HeaderTargetUriProperty); + set => SetValue(HeaderTargetUriProperty, value); + } + + public static readonly StyledProperty IconSourceProperty = + SettingsExpander.IconSourceProperty.AddOwner(); + + public IconSource? IconSource + { + get => GetValue(IconSourceProperty); + set => SetValue(IconSourceProperty, value); + } + + public static readonly StyledProperty IsConnectedProperty = AvaloniaProperty.Register< + SettingsAccountLinkExpander, + bool + >("IsConnected"); + + public bool IsConnected + { + get => GetValue(IsConnectedProperty); + set => SetValue(IsConnectedProperty, value); + } + + public static readonly StyledProperty OnDescriptionProperty = + AvaloniaProperty.Register( + "OnDescription", + Languages.Resources.Label_Connected + ); + + public object? OnDescription + { + get => GetValue(OnDescriptionProperty); + set => SetValue(OnDescriptionProperty, value); + } + + public static readonly StyledProperty OnDescriptionExtraProperty = + AvaloniaProperty.Register("OnDescriptionExtra"); + + public object? OnDescriptionExtra + { + get => GetValue(OnDescriptionExtraProperty); + set => SetValue(OnDescriptionExtraProperty, value); + } + + public static readonly StyledProperty OffDescriptionProperty = + AvaloniaProperty.Register("OffDescription"); + + public object? OffDescription + { + get => GetValue(OffDescriptionProperty); + set => SetValue(OffDescriptionProperty, value); + } + + public static readonly StyledProperty ConnectCommandProperty = + AvaloniaProperty.Register( + nameof(ConnectCommand), + enableDataValidation: true + ); + + public ICommand? ConnectCommand + { + get => GetValue(ConnectCommandProperty); + set => SetValue(ConnectCommandProperty, value); + } + + public static readonly StyledProperty DisconnectCommandProperty = + AvaloniaProperty.Register( + nameof(DisconnectCommand), + enableDataValidation: true + ); + + public ICommand? DisconnectCommand + { + get => GetValue(DisconnectCommandProperty); + set => SetValue(DisconnectCommandProperty, value); + } + + /*public static readonly StyledProperty IsLoading2Property = AvaloniaProperty.Register( + nameof(IsLoading2)); + + public bool IsLoading2 + { + get => GetValue(IsLoading2Property); + set => SetValue(IsLoading2Property, value); + }*/ + + private bool _isLoading; + + public static readonly DirectProperty IsLoadingProperty = + AvaloniaProperty.RegisterDirect( + "IsLoading", + o => o.IsLoading, + (o, v) => o.IsLoading = v + ); + + public bool IsLoading + { + get => _isLoading; + set => SetAndRaise(IsLoadingProperty, ref _isLoading, value); + } + + // ReSharper restore MemberCanBePrivate.Global + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + // Bind tapped event on header + if ( + HeaderTargetUri is { } headerTargetUri + && e.NameScope.Find("PART_HeaderTextBlock") is { } headerTextBlock + ) + { + headerTextBlock.Tapped += (_, _) => + { + ProcessRunner.OpenUrl(headerTargetUri.ToString()); + }; + } + + if (e.NameScope.Find("PART_SettingsExpander") is { } expander) + { + expander.ItemsSource = Items; + } + + if (ConnectCommand is { } command) + { + var connectButton = e.NameScope.Get - - - + ClipToBounds="True" + CornerRadius="8"> + - - - + + + + + + + HorizontalAlignment="Left" + VerticalAlignment="Top" + Classes="info"> + Text="{Binding CivitModel.Type}" /> - + HorizontalAlignment="Left" + VerticalAlignment="Top" + Classes="info"> + Text="{Binding CivitModel.BaseModelType}" /> - + HorizontalAlignment="Left" + VerticalAlignment="Top" + Classes="success" + IsVisible="{Binding ShowUpdateCard}"> + Text="{Binding UpdateCardText}" /> - + Background="#DD000000" + CornerRadius="8" + IsVisible="{Binding IsImporting}" + ZIndex="1" /> + IsVisible="{Binding IsImporting}" + Orientation="Vertical" + ZIndex="2"> + IsIndeterminate="False" + StartAngle="90" + Value="{Binding Value}" /> + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{Binding Text, TargetNullValue=Importing...}" /> - - - - - - - - + + - - + + + - + + HorizontalAlignment="Stretch" + KeyDown="InputElement_OnKeyDown" + Text="{Binding SearchQuery, Mode=TwoWay}" + Watermark="{x:Static lang:Resources.Label_ModelSearchWatermark}"> + + + + - + - + - + - - + + - + + Content="{x:Static lang:Resources.Label_ShowNsfwContent}" + IsChecked="{Binding ShowNsfw, Mode=TwoWay}" /> - - + + @@ -304,15 +406,15 @@ + VerticalAlignment="Bottom" + Text="{x:Static lang:Resources.Label_DataProvidedByCivitAi}" /> + HorizontalAlignment="Center" + IsVisible="{Binding HasSearched}" + Orientation="Vertical"> @@ -321,30 +423,31 @@ - @@ -352,19 +455,19 @@ + FontSize="20" + IsVisible="{Binding NoResultsFound}" + Text="{Binding NoResultsText, FallbackValue=No results found}" /> diff --git a/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs b/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs index 34e0887a..df465d31 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs @@ -1,5 +1,9 @@ -using Avalonia.Markup.Xaml; +using System.Diagnostics; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Views; @@ -16,4 +20,21 @@ public partial class CheckpointBrowserPage : UserControlBase { AvaloniaXamlLoader.Load(this); } + + private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) + { + if (sender is not ScrollViewer scrollViewer) + return; + + var isAtEnd = scrollViewer.Offset == scrollViewer.ScrollBarMaximum; + Debug.WriteLine($"IsAtEnd: {isAtEnd}"); + } + + private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && DataContext is CheckpointBrowserViewModel viewModel) + { + viewModel.ClearSearchQuery(); + } + } } diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml index 1c8d8d0b..a2f33f71 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml @@ -44,10 +44,15 @@ + + + + + @@ -215,6 +220,7 @@ @@ -368,7 +375,8 @@ - + - + MinWidth="220" + KeyDown="InputElement_OnKeyDown" + Text="{Binding SearchFilter, Mode=TwoWay}"> + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs index ee5c2946..34fcc7aa 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs @@ -1,15 +1,16 @@ using System; using System.Linq; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; using DynamicData.Binding; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.ViewModels; +using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Core.Attributes; -using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Models.FileInterfaces; using CheckpointFolder = StabilityMatrix.Avalonia.ViewModels.CheckpointManager.CheckpointFolder; namespace StabilityMatrix.Avalonia.Views; @@ -61,12 +62,36 @@ public partial class CheckpointsPage : UserControlBase } } - private static async void OnDrop(object? sender, DragEventArgs e) + private async void OnDrop(object? sender, DragEventArgs e) { var sourceDataContext = (e.Source as Control)?.DataContext; - if (sourceDataContext is CheckpointFolder folder) + switch (sourceDataContext) { - await folder.OnDrop(e); + case CheckpointFolder folder: + { + if (e.Data.Get("Context") is not CheckpointFile file) + return; + + var filePath = new FilePath(file.FilePath); + if (filePath.Directory?.FullPath != folder.DirectoryPath) + { + await folder.OnDrop(e); + } + break; + } + case CheckpointFile file: + { + if (e.Data.Get("Context") is not CheckpointFile dragFile) + return; + + var parentFolder = file.ParentFolder; + var dragFilePath = new FilePath(dragFile.FilePath); + if (dragFilePath.Directory?.FullPath != parentFolder.DirectoryPath) + { + await parentFolder.OnDrop(e); + } + break; + } } } @@ -79,7 +104,7 @@ public partial class CheckpointsPage : UserControlBase } } - private static void OnDragEnter(object? sender, DragEventArgs e) + private void OnDragEnter(object? sender, DragEventArgs e) { // Only allow Copy or Link as Drop Operations. e.DragEffects &= DragDropEffects.Copy | DragDropEffects.Link; @@ -92,9 +117,37 @@ public partial class CheckpointsPage : UserControlBase // Forward to view model var sourceDataContext = (e.Source as Control)?.DataContext; - if (sourceDataContext is CheckpointFolder folder) + switch (sourceDataContext) + { + case CheckpointFolder folder: + { + folder.IsExpanded = true; + if (e.Data.Get("Context") is not CheckpointFile file) + return; + + var filePath = new FilePath(file.FilePath); + folder.IsCurrentDragTarget = filePath.Directory?.FullPath != folder.DirectoryPath; + break; + } + case CheckpointFile file: + { + if (e.Data.Get("Context") is not CheckpointFile dragFile) + return; + + var parentFolder = file.ParentFolder; + var dragFilePath = new FilePath(dragFile.FilePath); + parentFolder.IsCurrentDragTarget = + dragFilePath.Directory?.FullPath != parentFolder.DirectoryPath; + break; + } + } + } + + private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && DataContext is CheckpointsPageViewModel vm) { - folder.IsCurrentDragTarget = true; + vm.ClearSearchQuery(); } } } diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml index 513144cd..2f453464 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml @@ -148,7 +148,6 @@ - @@ -322,8 +321,8 @@ - + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml new file mode 100644 index 00000000..98ab8228 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml.cs new file mode 100644 index 00000000..f5b44579 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views.Dialogs; + +[Transient] +public partial class LykosLoginDialog : UserControl +{ + public LykosLoginDialog() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml new file mode 100644 index 00000000..e0769f20 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml.cs new file mode 100644 index 00000000..0a32c242 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views.Dialogs; + +[Transient] +public partial class OAuthConnectDialog : UserControlBase +{ + public OAuthConnectDialog() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs index 57fc1c04..a1f930ba 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using AsyncImageLoader; using Avalonia; using Avalonia.Controls; @@ -26,6 +27,7 @@ using StabilityMatrix.Avalonia.Animations; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Extensions; using StabilityMatrix.Avalonia.Languages; +using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; @@ -44,7 +46,7 @@ namespace StabilityMatrix.Avalonia.Views; public partial class MainWindow : AppWindowBase { private readonly INotificationService notificationService; - private readonly INavigationService navigationService; + private readonly INavigationService navigationService; private FlyoutBase? progressFlyout; @@ -58,7 +60,7 @@ public partial class MainWindow : AppWindowBase public MainWindow( INotificationService notificationService, - INavigationService navigationService + INavigationService navigationService ) { this.notificationService = notificationService; @@ -74,6 +76,8 @@ public partial class MainWindow : AppWindowBase TitleBar.ExtendsContentIntoTitleBar = true; TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; + navigationService.TypedNavigation += NavigationService_OnTypedNavigation; + EventManager.Instance.ToggleProgressFlyout += (_, _) => progressFlyout?.Hide(); EventManager.Instance.CultureChanged += (_, _) => SetDefaultFonts(); EventManager.Instance.UpdateAvailable += OnUpdateAvailable; @@ -158,6 +162,15 @@ public partial class MainWindow : AppWindowBase } } + private void NavigationService_OnTypedNavigation(object? sender, TypedNavigationEventArgs e) + { + var mainViewModel = (MainWindowViewModel)DataContext!; + + mainViewModel.SelectedCategory = mainViewModel.Pages + .Concat(mainViewModel.FooterPages) + .FirstOrDefault(x => x.GetType() == e.ViewModelType); + } + private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo) { Dispatcher.UIThread.Post(() => diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml index 5f2da842..b9f29cfc 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml @@ -2,226 +2,243 @@ x:Class="StabilityMatrix.Avalonia.Views.OutputsPage" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit" xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:fluentAvalonia="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" + xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" - xmlns:ui="using:FluentAvalonia.UI.Controls" - xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" - xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:models1="clr-namespace:StabilityMatrix.Avalonia.Models" - xmlns:fluentAvalonia="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" xmlns:outputsPage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.OutputsPage" - xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard" - xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll" + xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard" + xmlns:ui="using:FluentAvalonia.UI.Controls" + xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" d:DataContext="{x:Static mocks:DesignData.OutputsPageViewModel}" - d:DesignHeight="450" - d:DesignWidth="700" + d:DesignHeight="650" + d:DesignWidth="800" x:CompileBindings="True" x:DataType="vm:OutputsPageViewModel" + Focusable="True" mc:Ignorable="d"> - - - - + + + + + + + + + + - - + - - - - - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + - + + VerticalAlignment="Top" + ItemsSource="{Binding Outputs}"> - + + Text="{x:Static lang:Resources.Action_Copy}" /> + Text="{x:Static lang:Resources.Action_OpenInExplorer}" /> + Text="{x:Static lang:Resources.Action_OpenInViewer}" /> + Text="{x:Static lang:Resources.Action_Delete}" /> - + - + + Text="{x:Static lang:Resources.Label_TextToImage}" /> + Text="{x:Static lang:Resources.Label_ImageToImage}" /> + Text="{x:Static lang:Resources.Label_Inpainting}" /> + CommandParameter="{Binding}" + HotKey="{x:Null}" + Text="{x:Static lang:Resources.Label_Upscale}"> - + diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs index 744d9482..b489897b 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs @@ -39,4 +39,12 @@ public partial class OutputsPage : UserControlBase e.Handled = true; } + + private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && DataContext is OutputsPageViewModel viewModel) + { + viewModel.ClearSearchQuery(); + } + } } diff --git a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml index 53525002..5cafe300 100644 --- a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml +++ b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml @@ -51,13 +51,17 @@ BorderBrush="#33000000" BorderThickness="2" CornerRadius="8"> - + - - + + - @@ -65,11 +69,11 @@ - - + diff --git a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml new file mode 100644 index 00000000..d5073ac8 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs new file mode 100644 index 00000000..0dbc0f50 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views.Settings; + +[Singleton] +public partial class AccountSettingsPage : UserControlBase +{ + public AccountSettingsPage() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml index 428c0483..a5abbfc3 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml @@ -1,16 +1,69 @@ - - Welcome to Avalonia! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs index 20cf1dd1..eea11ded 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs @@ -1,19 +1,13 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Views.Settings; [Singleton] -public partial class InferenceSettingsPage : UserControl +public partial class InferenceSettingsPage : UserControlBase { public InferenceSettingsPage() { InitializeComponent(); } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } } diff --git a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml new file mode 100644 index 00000000..04be95a7 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml.cs new file mode 100644 index 00000000..08dd277b --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Avalonia.Controls; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Models; +using StabilityMatrix.Avalonia.ViewModels.Settings; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models.Update; + +namespace StabilityMatrix.Avalonia.Views.Settings; + +[Singleton] +public partial class UpdateSettingsPage : UserControlBase +{ + public UpdateSettingsPage() + { + InitializeComponent(); + } + + private void ChannelListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + var listBox = (ListBox)sender!; + + if (e.AddedItems.Count == 0 || e.AddedItems[0] is not UpdateChannelCard item) + { + return; + } + + var vm = (UpdateSettingsViewModel)DataContext!; + + if (!vm.VerifyChannelSelection(item)) + { + listBox.Selection.Clear(); + + listBox.Selection.SelectedItem = vm.AvailableUpdateChannelCards.First( + c => c.UpdateChannel == UpdateChannel.Stable + ); + + vm.ShowLoginRequiredDialog(); + } + } +} diff --git a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml index b3e47a2b..db6a5b5c 100644 --- a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml @@ -2,19 +2,14 @@ x:Class="StabilityMatrix.Avalonia.Views.SettingsPage" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" - xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" - xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" - xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit" - xmlns:inference="clr-namespace:StabilityMatrix.Avalonia.Models.Inference" - xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight" + xmlns:vmBase="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base" + xmlns:local="clr-namespace:StabilityMatrix.Avalonia" Focusable="True" d:DataContext="{x:Static mocks:DesignData.SettingsViewModel}" d:DesignHeight="700" @@ -24,513 +19,33 @@ mc:Ignorable="d"> - + 24 + 17 + 6,3 + Medium - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -