Browse Source

Add folder deletion context menu

pull/55/head
Ionite 1 year ago
parent
commit
0721337f4d
No known key found for this signature in database
  1. 4
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  2. 37
      StabilityMatrix.Avalonia/DialogHelper.cs
  3. 71
      StabilityMatrix.Avalonia/Models/AdvancedObservableList.cs
  4. 8
      StabilityMatrix.Avalonia/Models/IRemovableListItem.cs
  5. 8
      StabilityMatrix.Avalonia/ViewModels/CheckpointFile.cs
  6. 78
      StabilityMatrix.Avalonia/ViewModels/CheckpointFolder.cs
  7. 11
      StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
  8. 22
      StabilityMatrix.Avalonia/ViewModels/ViewModelBase.cs
  9. 5
      StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml

4
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -129,7 +129,7 @@ public static class DesignData
{
Title = "Lora",
DirectoryPath = "Packages/lora",
CheckpointFiles = new ObservableCollection<CheckpointFile>
CheckpointFiles = new AdvancedObservableList<CheckpointFile>
{
new()
{
@ -162,7 +162,7 @@ public static class DesignData
{
Title = "VAE",
DirectoryPath = "Packages/VAE",
CheckpointFiles = new ObservableCollection<CheckpointFile>
CheckpointFiles = new AdvancedObservableList<CheckpointFile>
{
new()
{

37
StabilityMatrix.Avalonia/DialogHelper.cs

@ -4,8 +4,10 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
@ -132,6 +134,41 @@ public static class DialogHelper
IsPrimaryButtonEnabled = false,
};
}
/// <summary>
/// Create a simple title and description task dialog.
/// Sets the XamlRoot to the current top level window.
/// </summary>
public static TaskDialog CreateTaskDialog(string title, string description)
{
Dispatcher.UIThread.VerifyAccess();
var content = new StackPanel
{
Children =
{
new TextBlock
{
Margin = new Thickness(0,0,0,8),
FontSize = 16,
Text = title,
TextWrapping = TextWrapping.WrapWithOverflow,
},
new TextBlock
{
Text = description,
TextWrapping = TextWrapping.WrapWithOverflow,
}
}
};
return new TaskDialog
{
Title = title,
Content = content,
XamlRoot = App.VisualRoot
};
}
}
// Text fields

71
StabilityMatrix.Avalonia/Models/AdvancedObservableList.cs

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Threading;
namespace StabilityMatrix.Avalonia.Models;
/// <summary>
/// Observable AvaloniaList supporting child item deletion requests.
/// </summary>
public class AdvancedObservableList<T> : AvaloniaList<T>
{
/// <inheritdoc />
public AdvancedObservableList()
{
CollectionChanged += CollectionChangedEventRegistrationHandler;
}
/// <inheritdoc />
public AdvancedObservableList(IEnumerable<T> items) : base(items)
{
CollectionChanged += CollectionChangedEventRegistrationHandler;
}
private void CollectionChangedEventRegistrationHandler(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
TryUnregisterRemovableListItem((T)item);
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
TryRegisterRemovableListItem((T)item);
}
}
}
private void OnItemRemoveRequested(object? sender, EventArgs e)
{
if (sender is T item)
{
Dispatcher.UIThread.Post(() => Remove(item));
}
}
private bool TryRegisterRemovableListItem(T item)
{
if (item is IRemovableListItem removableListItem)
{
removableListItem.ParentListRemoveRequested += OnItemRemoveRequested;
return true;
}
return false;
}
private bool TryUnregisterRemovableListItem(T item)
{
if (item is IRemovableListItem removableListItem)
{
removableListItem.ParentListRemoveRequested -= OnItemRemoveRequested;
return true;
}
return false;
}
}

8
StabilityMatrix.Avalonia/Models/IRemovableListItem.cs

@ -0,0 +1,8 @@
using System;
namespace StabilityMatrix.Avalonia.Models;
public interface IRemovableListItem
{
public event EventHandler ParentListRemoveRequested;
}

8
StabilityMatrix.Avalonia/ViewModels/CheckpointFile.cs

@ -5,10 +5,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Data;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
@ -25,9 +22,6 @@ public partial class CheckpointFile : ViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
// Event for when this file is deleted
public event EventHandler<CheckpointFile>? Deleted;
/// <summary>
/// Absolute path to the checkpoint file.
/// </summary>
@ -122,7 +116,7 @@ public partial class CheckpointFile : ViewModelBase
IsLoading = false;
}
}
Deleted?.Invoke(this, this);
RemoveFromParentList();
}
[RelayCommand]

78
StabilityMatrix.Avalonia/ViewModels/CheckpointFolder.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
@ -9,10 +8,10 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using AvaloniaEdit.Utils;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
@ -77,9 +76,9 @@ public partial class CheckpointFolder : ViewModelBase
public ProgressViewModel Progress { get; } = new();
public CheckpointFolder? ParentFolder { get; init; }
public ObservableCollection<CheckpointFolder> SubFolders { get; init; } = new();
public ObservableCollection<CheckpointFile> CheckpointFiles { get; init; } = new();
public ObservableCollection<CheckpointFile> DisplayedCheckpointFiles { get; set; }
public AdvancedObservableList<CheckpointFolder> SubFolders { get; init; } = new();
public AdvancedObservableList<CheckpointFile> CheckpointFiles { get; init; } = new();
public AdvancedObservableList<CheckpointFile> DisplayedCheckpointFiles { get; set; }
public CheckpointFolder(
ISettingsManager settingsManager,
@ -121,7 +120,7 @@ public partial class CheckpointFolder : ViewModelBase
{
var filteredFiles = CheckpointFiles.Where(y =>
y.FileName.Contains(value, StringComparison.OrdinalIgnoreCase));
DisplayedCheckpointFiles = new ObservableCollection<CheckpointFile>(filteredFiles);
DisplayedCheckpointFiles = new AdvancedObservableList<CheckpointFile>(filteredFiles);
}
}
@ -137,30 +136,9 @@ public partial class CheckpointFolder : ViewModelBase
}
}
// On collection changes
private void OnCheckpointFilesChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(TitleWithFilesCount));
if (e.NewItems == null) return;
// On new added items, add event handler for deletion
foreach (CheckpointFile item in e.NewItems)
{
item.Deleted += OnCheckpointFileDelete;
}
}
/// <summary>
/// Handler for CheckpointFile requesting to be deleted from the collection.
/// </summary>
/// <param name="sender"></param>
/// <param name="file"></param>
private void OnCheckpointFileDelete(object? sender, CheckpointFile file)
{
Dispatcher.UIThread.Post(() =>
{
CheckpointFiles.Remove(file);
DisplayedCheckpointFiles.Remove(file);
});
}
public async Task OnDrop(DragEventArgs e)
@ -190,6 +168,52 @@ public partial class CheckpointFolder : ViewModelBase
await ProcessRunner.OpenFolderBrowser(path);
}
[RelayCommand]
private async Task Delete()
{
var directory = new DirectoryPath(DirectoryPath);
if (!directory.Exists)
{
RemoveFromParentList();
return;
}
var dialog = DialogHelper.CreateTaskDialog(
"Are you sure you want to delete this folder?",directory);
dialog.ShowProgressBar = false;
dialog.Buttons = new List<TaskDialogButton>
{
TaskDialogButton.YesButton,
TaskDialogButton.NoButton
};
dialog.Closing += async (sender, e) =>
{
// We only want to use the deferral on the 'Yes' Button
if ((TaskDialogStandardResult)e.Result == TaskDialogStandardResult.Yes)
{
var deferral = e.GetDeferral();
sender.ShowProgressBar = true;
sender.SetProgressBarState(0, TaskDialogProgressState.Indeterminate);
await using (new MinimumDelay(200, 300))
{
await directory.DeleteAsync(true);
}
RemoveFromParentList();
deferral.Complete();
}
};
dialog.XamlRoot = App.VisualRoot;
await dialog.ShowAsync(true);
}
[RelayCommand]
private async Task CreateSubFolder()
{

11
StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs

@ -36,7 +36,9 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
[ObservableProperty] private bool isImportAsConnected;
[ObservableProperty] private bool isLoading;
[ObservableProperty] private bool isIndexing;
[ObservableProperty] private string searchFilter;
[ObservableProperty]
private string searchFilter = string.Empty;
partial void OnIsImportAsConnectedChanged(bool value)
{
@ -47,7 +49,8 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
}
}
[ObservableProperty] private ObservableCollection<CheckpointFolder> checkpointFolders = new();
[ObservableProperty]
private ObservableCollection<CheckpointFolder> checkpointFolders = new();
[ObservableProperty]
private ObservableCollection<CheckpointFolder> displayedCheckpointFolders = new();
@ -70,7 +73,8 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
// Set UI states
IsImportAsConnected = settingsManager.Settings.IsImportAsConnected;
SearchFilter = string.Empty;
// Refresh search filter
OnSearchFilterChanged(string.Empty);
if (Design.IsDesignMode) return;
@ -81,6 +85,7 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
IsIndexing = false;
}
// ReSharper disable once UnusedParameterInPartialMethod
partial void OnSearchFilterChanged(string value)
{
var filteredFolders = CheckpointFolders

22
StabilityMatrix.Avalonia/ViewModels/ViewModelBase.cs

@ -1,10 +1,28 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Avalonia.Models;
namespace StabilityMatrix.Avalonia.ViewModels;
public class ViewModelBase : ObservableValidator
public class ViewModelBase : ObservableValidator, IRemovableListItem
{
private WeakEventManager? parentListRemoveRequestedEventManager;
public event EventHandler ParentListRemoveRequested
{
add
{
parentListRemoveRequestedEventManager ??= new WeakEventManager();
parentListRemoveRequestedEventManager.AddEventHandler(value);
}
remove => parentListRemoveRequestedEventManager?.RemoveEventHandler(value);
}
protected void RemoveFromParentList() => parentListRemoveRequestedEventManager?.RaiseEvent(
this, EventArgs.Empty, nameof(ParentListRemoveRequested));
public virtual void OnLoaded()
{

5
StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml

@ -216,6 +216,11 @@
<ui:MenuFlyoutItem Text="Show in Explorer" IconSource="Open"
Command="{Binding ShowInExplorerCommand}"
CommandParameter="{Binding DirectoryPath}"/>
<!-- Only allow deletion of non-root folders (Parent is not null) -->
<ui:MenuFlyoutItem Text="Delete" IconSource="Delete"
IsEnabled="{Binding ParentFolder, Converter={x:Static ObjectConverters.IsNotNull}}"
IsVisible="{Binding ParentFolder, Converter={x:Static ObjectConverters.IsNotNull}}"
Command="{Binding DeleteCommand}"/>
<ui:MenuFlyoutSeparator/>
<ui:MenuFlyoutSubItem Text="New" IconSource="Add">
<ui:MenuFlyoutSubItem.Items>

Loading…
Cancel
Save