diff --git a/src/KeyKeeper/Models/GroupData.cs b/src/KeyKeeper/Models/GroupData.cs
new file mode 100644
index 0000000..7b964be
--- /dev/null
+++ b/src/KeyKeeper/Models/GroupData.cs
@@ -0,0 +1,5 @@
+using System;
+
+namespace KeyKeeper.Models;
+
+public record GroupEditData(string Name, Guid IconType);
diff --git a/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs b/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs
index 48db0af..3790d6d 100644
--- a/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs
+++ b/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs
@@ -160,6 +160,29 @@ public class UnlockedRepositoryViewModel : ViewModelBase
OnPropertyChanged(nameof(Passwords));
}
+ public void UpdateGroup(PassStoreEntryGroup group, string newName, Guid newIconType)
+ {
+ group.Name = newName;
+ group.IconType = newIconType;
+ group.ModificationDate = DateTime.UtcNow;
+ HasUnsavedChanges = true;
+ OnPropertyChanged(nameof(PasswordGroups));
+ }
+
+ public void DeleteGroup(PassStoreEntryGroup group)
+ {
+ if (rootDirectory == null)
+ return;
+
+ passStore.DeleteEntry(rootDirectory, group.Id);
+ if (currentDirectory == group && rootDirectory != null)
+ {
+ ChangeDirectory(rootDirectory.ChildGroups.FirstOrDefault() ?? rootDirectory);
+ }
+ HasUnsavedChanges = true;
+ OnPropertyChanged(nameof(PasswordGroups));
+ }
+
public void Save()
{
passStore.Save();
diff --git a/src/KeyKeeper/Views/CloseConfirmationDialog.axaml b/src/KeyKeeper/Views/CloseConfirmationDialog.axaml
index 2779e45..df19cdf 100644
--- a/src/KeyKeeper/Views/CloseConfirmationDialog.axaml
+++ b/src/KeyKeeper/Views/CloseConfirmationDialog.axaml
@@ -16,6 +16,7 @@
diff --git a/src/KeyKeeper/Views/CreateGroupDialog.axaml.cs b/src/KeyKeeper/Views/CreateGroupDialog.axaml.cs
index ebbacee..bdb56d4 100644
--- a/src/KeyKeeper/Views/CreateGroupDialog.axaml.cs
+++ b/src/KeyKeeper/Views/CreateGroupDialog.axaml.cs
@@ -3,15 +3,14 @@ using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
+using KeyKeeper.Models;
using KeyKeeper.PasswordStore;
namespace KeyKeeper.Views;
public partial class CreateGroupDialog : Window
{
- public string GroupName { get; private set; } = string.Empty;
- public Guid IconType { get; private set; } = BuiltinEntryIconType.DEFAULT;
- public bool Success { get; private set; }
+ public GroupEditData? GroupData { get; private set; }
private record IconChoice(Guid Id)
{
@@ -29,28 +28,44 @@ public partial class CreateGroupDialog : Window
IconListBox.ItemsSource = icons;
IconListBox.SelectedIndex = 0;
- NameTextBox.TextChanged += (_, _) => UpdateCreateButtonState();
+ NameTextBox.TextChanged += (_, _) => UpdateActionButtonState();
KeyDown += OnKeyDown;
}
- private void UpdateCreateButtonState()
+ public void SetupForEdit(PassStoreEntryGroup group)
{
- CreateButton.IsEnabled = !string.IsNullOrWhiteSpace(NameTextBox.Text);
+ TitleText.Text = "Edit group";
+ ActionButton.Content = "Save";
+ NameTextBox.Text = group.Name;
+
+ for (int i = 0; i < IconListBox.ItemCount; i++)
+ {
+ if (IconListBox.Items[i] is IconChoice choice && choice.Id == group.IconType)
+ {
+ IconListBox.SelectedIndex = i;
+ break;
+ }
+ }
+ }
+
+ private void UpdateActionButtonState()
+ {
+ ActionButton.IsEnabled = !string.IsNullOrWhiteSpace(NameTextBox.Text);
}
private void OnKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
Close();
- else if (e.Key == Key.Enter && CreateButton.IsEnabled)
+ else if (e.Key == Key.Enter && ActionButton.IsEnabled)
Submit();
}
- private void CreateButton_Click(object? sender, RoutedEventArgs e) => Submit();
+ private void ActionButton_Click(object? sender, RoutedEventArgs e) => Submit();
private void CancelButton_Click(object? sender, RoutedEventArgs e)
{
- Success = false;
+ GroupData = null;
Close();
}
@@ -64,11 +79,11 @@ public partial class CreateGroupDialog : Window
return;
}
- GroupName = name;
+ var iconType = BuiltinEntryIconType.DEFAULT;
if (IconListBox.SelectedItem is IconChoice choice)
- IconType = choice.Id;
+ iconType = choice.Id;
- Success = true;
+ GroupData = new GroupEditData(name, iconType);
Close();
}
}
diff --git a/src/KeyKeeper/Views/RepositoryWindow.axaml b/src/KeyKeeper/Views/RepositoryWindow.axaml
index 260c37d..fa161c9 100644
--- a/src/KeyKeeper/Views/RepositoryWindow.axaml
+++ b/src/KeyKeeper/Views/RepositoryWindow.axaml
@@ -69,6 +69,12 @@
DoubleTapped="Entry_DoubleTapped">
+
+
+
+
+
+
diff --git a/src/KeyKeeper/Views/RepositoryWindow.axaml.cs b/src/KeyKeeper/Views/RepositoryWindow.axaml.cs
index b0065ce..2fad556 100644
--- a/src/KeyKeeper/Views/RepositoryWindow.axaml.cs
+++ b/src/KeyKeeper/Views/RepositoryWindow.axaml.cs
@@ -1,10 +1,12 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Input;
using KeyKeeper.PasswordStore;
+using KeyKeeper.Models;
using KeyKeeper.ViewModels;
using Avalonia.VisualTree;
using Avalonia.Controls.Presenters;
@@ -180,14 +182,14 @@ public partial class RepositoryWindow : Window
vm_.StartLockTimer();
- if (dialog.Success)
+ if (dialog.GroupData != null)
{
var group = new PassStoreEntryGroup(
Guid.NewGuid(),
DateTime.UtcNow,
DateTime.UtcNow,
- dialog.IconType,
- dialog.GroupName,
+ dialog.GroupData.IconType,
+ dialog.GroupData.Name,
FileFormatConstants.GROUP_TYPE_SIMPLE
);
vm.AddGroup(group);
@@ -304,6 +306,76 @@ public partial class RepositoryWindow : Window
_contextMenuEntry = null;
}
+ private async void GroupContextMenuItem_Click(object sender, RoutedEventArgs args)
+ {
+ if (args.Source is not StyledElement s || s.DataContext is not PassStoreEntryGroup group)
+ return;
+
+ if (DataContext is not RepositoryWindowViewModel vm ||
+ vm.CurrentPage is not UnlockedRepositoryViewModel pageVm)
+ return;
+
+ if (group.GroupType == FileFormatConstants.GROUP_TYPE_DEFAULT ||
+ group.GroupType == FileFormatConstants.GROUP_TYPE_FAVOURITES ||
+ group.GroupType == FileFormatConstants.GROUP_TYPE_ROOT)
+ return;
+
+ if (s.Name == "groupCtxMenuEdit")
+ {
+ await EditGroup(vm, pageVm, group);
+ }
+ else if (s.Name == "groupCtxMenuDelete")
+ {
+ await DeleteGroup(group);
+ }
+ }
+
+ private async Task EditGroup(RepositoryWindowViewModel vm, UnlockedRepositoryViewModel pageVm, PassStoreEntryGroup group)
+ {
+ CreateGroupDialog dialog = new();
+ dialog.SetupForEdit(group);
+
+ vm.StopLockTimer();
+
+ await dialog.ShowDialog(this);
+
+ vm.StartLockTimer();
+
+ if (dialog.GroupData != null)
+ {
+ pageVm.UpdateGroup(group, dialog.GroupData.Name, dialog.GroupData.IconType);
+ this.FindControlRecursive("NotificationHost")?.Show("Group updated");
+ }
+ }
+
+ private async Task DeleteGroup(PassStoreEntryGroup group)
+ {
+ ConfirmationDialog confirmDialog = new();
+ confirmDialog.SetContent(
+ "Delete Group",
+ $"Are you sure you want to delete the group '{group.DisplayName}'? This action cannot be undone.",
+ "Delete"
+ );
+
+ if (DataContext is not RepositoryWindowViewModel vm)
+ return;
+
+ vm.StopLockTimer();
+
+ await confirmDialog.ShowDialog(this);
+
+ vm.StartLockTimer();
+
+ if (confirmDialog.Confirmed)
+ {
+ if (vm.CurrentPage is UnlockedRepositoryViewModel pageVm)
+ {
+ pageVm.DeleteGroup(group);
+ this.FindControlRecursive("NotificationHost")?.Show("Group deleted");
+ }
+ }
+ }
+
private async void EntryContextMenuItem_Click(object sender, RoutedEventArgs args)
{
if (args.Source is StyledElement s && s.DataContext is PassStoreEntry ent)