Skip to content

Commit f0d401b

Browse files
committed
Added drag-n-drop between folders, X buttons to all search boxes, rearranged InstallerDialog, and fixed outputs page reverting selected category on page load
1 parent 7bf16db commit f0d401b

File tree

14 files changed

+294
-94
lines changed

14 files changed

+294
-94
lines changed

CHANGELOG.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
77

88
## v2.7.0-dev.2
99
### Added
10-
- Outputs Page
11-
- Added Refresh button to update gallery from file system changes
12-
### Changed
13-
- Outputs Page
14-
- Updated button and menu layout
10+
#### General
11+
- Added an X button to all search fields to instantly clear them (Esc key also works)
12+
#### Outputs Page
13+
- Added Refresh button to update gallery from file system changes
14+
#### Checkpoints Page
15+
- Added the ability to drag & drop checkpoints between different folders
16+
## Changed
17+
#### Outputs Page
18+
- Updated button and menu layout
19+
#### Packages Page
20+
- Rearranged Add Package dialog slightly to accommodate longer package list
1521
### Fixed
1622
- Fixed InvalidOperation errors when signing into accounts shortly after signing out, while the previous account update is still running
23+
- Fixed Outputs page reverting back to Shared Output Folder every time the page is reloaded
1724

1825
## v2.7.0-dev.1
1926
### Added

StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,11 @@ public void LastPage()
534534
CurrentPageNumber = TotalPages;
535535
}
536536

537+
public void ClearSearchQuery()
538+
{
539+
SearchQuery = string.Empty;
540+
}
541+
537542
partial void OnShowNsfwChanged(bool value)
538543
{
539544
settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled, value);

StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ private async Task DeleteAsync()
136136
RemoveFromParentList();
137137
}
138138

139+
public void OnMoved() => RemoveFromParentList();
140+
139141
[RelayCommand]
140142
private async Task RenameAsync()
141143
{

StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using StabilityMatrix.Avalonia.Services;
1919
using StabilityMatrix.Avalonia.ViewModels.Base;
2020
using StabilityMatrix.Core.Attributes;
21+
using StabilityMatrix.Core.Exceptions;
2122
using StabilityMatrix.Core.Extensions;
2223
using StabilityMatrix.Core.Helper;
2324
using StabilityMatrix.Core.Models;
@@ -212,6 +213,10 @@ public async Task OnDrop(DragEventArgs e)
212213
var paths = files.Select(f => f.Path.LocalPath).ToArray();
213214
await ImportFilesAsync(paths, settingsManager.Settings.IsImportAsConnected);
214215
}
216+
else if (e.Data.Get("Context") is CheckpointFile file)
217+
{
218+
await MoveBetweenFolders(file);
219+
}
215220
}
216221
catch (Exception)
217222
{
@@ -320,6 +325,71 @@ private async Task CreateSubFolder()
320325
}
321326
}
322327

328+
public async Task MoveBetweenFolders(CheckpointFile sourceFile)
329+
{
330+
var delay = 1.5f;
331+
try
332+
{
333+
Progress.Value = 0;
334+
var sourcePath = new FilePath(sourceFile.FilePath);
335+
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(sourcePath);
336+
var sourceCmInfoPath = Path.Combine(
337+
sourcePath.Directory,
338+
$"{fileNameWithoutExt}.cm-info.json"
339+
);
340+
var sourcePreviewPath = Path.Combine(
341+
sourcePath.Directory,
342+
$"{fileNameWithoutExt}.preview.jpeg"
343+
);
344+
var destinationFilePath = Path.Combine(DirectoryPath, sourcePath.Name);
345+
var destinationCmInfoPath = Path.Combine(
346+
DirectoryPath,
347+
$"{fileNameWithoutExt}.cm-info.json"
348+
);
349+
var destinationPreviewPath = Path.Combine(
350+
DirectoryPath,
351+
$"{fileNameWithoutExt}.preview.jpeg"
352+
);
353+
354+
// Move files
355+
if (File.Exists(sourcePath))
356+
{
357+
Progress.Text = $"Moving {sourcePath.Name}...";
358+
await FileTransfers.MoveFileAsync(sourcePath, destinationFilePath);
359+
}
360+
361+
Progress.Value = 33;
362+
Progress.Text = $"Moving {sourcePath.Name} metadata...";
363+
364+
if (File.Exists(sourceCmInfoPath))
365+
{
366+
await FileTransfers.MoveFileAsync(sourceCmInfoPath, destinationCmInfoPath);
367+
}
368+
369+
Progress.Value = 66;
370+
371+
if (File.Exists(sourcePreviewPath))
372+
{
373+
await FileTransfers.MoveFileAsync(sourcePreviewPath, destinationPreviewPath);
374+
}
375+
376+
Progress.Value = 100;
377+
Progress.Text = $"Moved {sourcePath.Name} to {Title}";
378+
sourceFile.OnMoved();
379+
BackgroundIndex();
380+
delay = 0.5f;
381+
}
382+
catch (FileTransferExistsException)
383+
{
384+
Progress.Value = 0;
385+
Progress.Text = "Failed to move file: destination file exists";
386+
}
387+
finally
388+
{
389+
DelayedClearProgress(TimeSpan.FromSeconds(delay));
390+
}
391+
}
392+
323393
/// <summary>
324394
/// Imports files to the folder. Reports progress to instance properties.
325395
/// </summary>

StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ public override void OnLoaded()
124124
Logger.Info($"OnLoadedAsync in {sw.ElapsedMilliseconds}ms");
125125
}
126126

127+
public void ClearSearchQuery()
128+
{
129+
SearchFilter = string.Empty;
130+
}
131+
127132
// ReSharper disable once UnusedParameterInPartialMethod
128133
partial void OnSearchFilterChanged(string value)
129134
{

StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ public partial class OutputsPageViewModel : PageViewModelBase
6969

7070
[ObservableProperty]
7171
[NotifyPropertyChangedFor(nameof(CanShowOutputTypes))]
72-
private PackageOutputCategory selectedCategory;
72+
private PackageOutputCategory? selectedCategory;
7373

7474
[ObservableProperty]
75-
private SharedOutputType selectedOutputType;
75+
private SharedOutputType? selectedOutputType;
7676

7777
[ObservableProperty]
7878
[NotifyPropertyChangedFor(nameof(NumImagesSelected))]
@@ -136,6 +136,8 @@ ILogger<OutputsPageViewModel> logger
136136
settings => settings.OutputsImageSize,
137137
delay: TimeSpan.FromMilliseconds(250)
138138
);
139+
140+
RefreshCategories();
139141
}
140142

141143
public override void OnLoaded()
@@ -148,49 +150,8 @@ public override void OnLoaded()
148150

149151
Directory.CreateDirectory(settingsManager.ImagesDirectory);
150152

151-
var packageCategories = settingsManager.Settings.InstalledPackages
152-
.Where(x => !x.UseSharedOutputFolder)
153-
.Select(packageFactory.GetPackagePair)
154-
.WhereNotNull()
155-
.Where(
156-
p =>
157-
p.BasePackage.SharedOutputFolders != null
158-
&& p.BasePackage.SharedOutputFolders.Any()
159-
)
160-
.Select(
161-
pair =>
162-
new PackageOutputCategory
163-
{
164-
Path = Path.Combine(
165-
pair.InstalledPackage.FullPath!,
166-
pair.BasePackage.OutputFolderName
167-
),
168-
Name = pair.InstalledPackage.DisplayName ?? ""
169-
}
170-
)
171-
.ToList();
172-
173-
packageCategories.Insert(
174-
0,
175-
new PackageOutputCategory
176-
{
177-
Path = settingsManager.ImagesDirectory,
178-
Name = "Shared Output Folder"
179-
}
180-
);
181-
182-
packageCategories.Insert(
183-
1,
184-
new PackageOutputCategory
185-
{
186-
Path = settingsManager.ImagesInferenceDirectory,
187-
Name = "Inference"
188-
}
189-
);
190-
191-
Categories = new ObservableCollection<PackageOutputCategory>(packageCategories);
192-
SelectedCategory = Categories.First();
193-
SelectedOutputType = SharedOutputType.All;
153+
SelectedCategory ??= Categories.First();
154+
SelectedOutputType ??= SharedOutputType.All;
194155
SearchQuery = string.Empty;
195156
ImageSize = settingsManager.Settings.OutputsImageSize;
196157

@@ -216,14 +177,14 @@ partial void OnSelectedCategoryChanged(
216177
GetOutputs(path);
217178
}
218179

219-
partial void OnSelectedOutputTypeChanged(SharedOutputType oldValue, SharedOutputType newValue)
180+
partial void OnSelectedOutputTypeChanged(SharedOutputType? oldValue, SharedOutputType? newValue)
220181
{
221-
if (oldValue == newValue)
182+
if (oldValue == newValue || newValue == null)
222183
return;
223184

224185
var path =
225186
newValue == SharedOutputType.All
226-
? SelectedCategory.Path
187+
? SelectedCategory?.Path
227188
: Path.Combine(SelectedCategory.Path, newValue.ToString());
228189
GetOutputs(path);
229190
}
@@ -299,6 +260,7 @@ public Task CopyImage(string imagePath)
299260

300261
public void Refresh()
301262
{
263+
Dispatcher.UIThread.Post(RefreshCategories);
302264
Dispatcher.UIThread.Post(OnLoaded);
303265
}
304266

@@ -502,6 +464,11 @@ var path in Directory.EnumerateFiles(
502464
IsConsolidating = false;
503465
}
504466

467+
public void ClearSearchQuery()
468+
{
469+
SearchQuery = string.Empty;
470+
}
471+
505472
private void GetOutputs(string directory)
506473
{
507474
if (!settingsManager.IsLibraryDirSet)
@@ -533,4 +500,60 @@ private void GetOutputs(string directory)
533500
OutputsCache.EditDiff(files);
534501
}
535502
}
503+
504+
private void RefreshCategories()
505+
{
506+
if (Design.IsDesignMode)
507+
return;
508+
509+
if (!settingsManager.IsLibraryDirSet)
510+
return;
511+
512+
var previouslySelectedCategory = SelectedCategory;
513+
514+
var packageCategories = settingsManager.Settings.InstalledPackages
515+
.Where(x => !x.UseSharedOutputFolder)
516+
.Select(packageFactory.GetPackagePair)
517+
.WhereNotNull()
518+
.Where(
519+
p =>
520+
p.BasePackage.SharedOutputFolders != null
521+
&& p.BasePackage.SharedOutputFolders.Any()
522+
)
523+
.Select(
524+
pair =>
525+
new PackageOutputCategory
526+
{
527+
Path = Path.Combine(
528+
pair.InstalledPackage.FullPath!,
529+
pair.BasePackage.OutputFolderName
530+
),
531+
Name = pair.InstalledPackage.DisplayName ?? ""
532+
}
533+
)
534+
.ToList();
535+
536+
packageCategories.Insert(
537+
0,
538+
new PackageOutputCategory
539+
{
540+
Path = settingsManager.ImagesDirectory,
541+
Name = "Shared Output Folder"
542+
}
543+
);
544+
545+
packageCategories.Insert(
546+
1,
547+
new PackageOutputCategory
548+
{
549+
Path = settingsManager.ImagesInferenceDirectory,
550+
Name = "Inference"
551+
}
552+
);
553+
554+
Categories = new ObservableCollection<PackageOutputCategory>(packageCategories);
555+
SelectedCategory =
556+
Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name)
557+
?? Categories.First();
558+
}
536559
}

StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,17 @@
278278
<TextBox
279279
HorizontalAlignment="Stretch"
280280
Margin="8,0,0,0"
281+
KeyDown="InputElement_OnKeyDown"
281282
Watermark="{x:Static lang:Resources.Label_ModelSearchWatermark}"
282-
Text="{Binding SearchQuery, Mode=TwoWay}" />
283+
Text="{Binding SearchQuery, Mode=TwoWay}">
284+
<TextBox.InnerRightContent>
285+
<Button Classes="transparent-full"
286+
IsVisible="{Binding SearchQuery.Length}"
287+
Command="{Binding ClearSearchQuery}">
288+
<ui:SymbolIcon Symbol="Cancel" />
289+
</Button>
290+
</TextBox.InnerRightContent>
291+
</TextBox>
283292

284293
<Button
285294
Classes="accent"
@@ -348,7 +357,8 @@
348357
</StackPanel>
349358

350359
<ScrollViewer Grid.Row="1"
351-
Margin="8,0,8,0">
360+
Margin="8,0,8,0"
361+
ScrollChanged="ScrollViewer_OnScrollChanged">
352362
<ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}"
353363
ItemsSource="{Binding ModelCards}">
354364
<ItemsRepeater.Layout>

StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
using Avalonia.Markup.Xaml;
1+
using System.Diagnostics;
2+
using Avalonia.Controls;
3+
using Avalonia.Input;
4+
using Avalonia.Markup.Xaml;
25
using StabilityMatrix.Avalonia.Controls;
6+
using StabilityMatrix.Avalonia.ViewModels;
37
using StabilityMatrix.Core.Attributes;
48

59
namespace StabilityMatrix.Avalonia.Views;
@@ -16,4 +20,21 @@ private void InitializeComponent()
1620
{
1721
AvaloniaXamlLoader.Load(this);
1822
}
23+
24+
private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)
25+
{
26+
if (sender is not ScrollViewer scrollViewer)
27+
return;
28+
29+
var isAtEnd = scrollViewer.Offset == scrollViewer.ScrollBarMaximum;
30+
Debug.WriteLine($"IsAtEnd: {isAtEnd}");
31+
}
32+
33+
private void InputElement_OnKeyDown(object? sender, KeyEventArgs e)
34+
{
35+
if (e.Key == Key.Escape && DataContext is CheckpointBrowserViewModel viewModel)
36+
{
37+
viewModel.ClearSearchQuery();
38+
}
39+
}
1940
}

0 commit comments

Comments
 (0)