mirror of
https://github.com/KeyKeeperApp/KeyKeeper.git
synced 2026-05-19 06:46:32 +03:00
add group tree display
This commit is contained in:
@@ -20,6 +20,7 @@ public abstract class PassStoreEntry
|
|||||||
return $"avares://KeyKeeper/Assets/builtin-entry-icon-{IconType}.svg";
|
return $"avares://KeyKeeper/Assets/builtin-entry-icon-{IconType}.svg";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public virtual string DisplayName => Name;
|
||||||
|
|
||||||
public void WriteToStream(Stream str)
|
public void WriteToStream(Stream str)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using static KeyKeeper.PasswordStore.FileFormatConstants;
|
using static KeyKeeper.PasswordStore.FileFormatConstants;
|
||||||
|
|
||||||
namespace KeyKeeper.PasswordStore;
|
namespace KeyKeeper.PasswordStore;
|
||||||
@@ -12,6 +13,16 @@ public class PassStoreEntryGroup : PassStoreEntry, IPassStoreDirectory
|
|||||||
public Guid? CustomGroupSubtype { get; set; }
|
public Guid? CustomGroupSubtype { get; set; }
|
||||||
public List<PassStoreEntry> ChildEntries { get; set; }
|
public List<PassStoreEntry> ChildEntries { get; set; }
|
||||||
|
|
||||||
|
public override string DisplayName => GroupType switch
|
||||||
|
{
|
||||||
|
GROUP_TYPE_DEFAULT => "All Passwords",
|
||||||
|
GROUP_TYPE_FAVOURITES => "Favourites",
|
||||||
|
GROUP_TYPE_ROOT => ":root:",
|
||||||
|
_ => Name
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<PassStoreEntryGroup> ChildGroups => ChildEntries.OfType<PassStoreEntryGroup>();
|
||||||
|
|
||||||
public PassStoreEntryGroup(Guid id, DateTime createdAt, DateTime modifiedAt,
|
public PassStoreEntryGroup(Guid id, DateTime createdAt, DateTime modifiedAt,
|
||||||
Guid iconType, string name, byte groupType,
|
Guid iconType, string name, byte groupType,
|
||||||
List<PassStoreEntry>? children = null,
|
List<PassStoreEntry>? children = null,
|
||||||
|
|||||||
@@ -269,6 +269,15 @@ public class PassStoreFileAccessor : IPassStore
|
|||||||
"",
|
"",
|
||||||
GROUP_TYPE_DEFAULT
|
GROUP_TYPE_DEFAULT
|
||||||
);
|
);
|
||||||
|
PassStoreEntryGroup favourites = new(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
DateTime.UtcNow,
|
||||||
|
DateTime.UtcNow,
|
||||||
|
Guid.Empty,
|
||||||
|
"",
|
||||||
|
GROUP_TYPE_FAVOURITES
|
||||||
|
);
|
||||||
|
|
||||||
PassStoreEntryGroup root = new(
|
PassStoreEntryGroup root = new(
|
||||||
Guid.NewGuid(),
|
Guid.NewGuid(),
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
@@ -276,9 +285,10 @@ public class PassStoreFileAccessor : IPassStore
|
|||||||
Guid.Empty,
|
Guid.Empty,
|
||||||
"",
|
"",
|
||||||
GROUP_TYPE_ROOT,
|
GROUP_TYPE_ROOT,
|
||||||
[defaultGroup]
|
[defaultGroup, favourites]
|
||||||
);
|
);
|
||||||
defaultGroup.Parent = root;
|
defaultGroup.Parent = root;
|
||||||
|
favourites.Parent = root;
|
||||||
root.WriteToStream(w);
|
root.WriteToStream(w);
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class UnlockedRepositoryViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
private IPassStore passStore;
|
private IPassStore passStore;
|
||||||
private IPassStoreDirectory currentDirectory;
|
private IPassStoreDirectory currentDirectory;
|
||||||
|
private PassStoreEntryGroup? rootDirectory;
|
||||||
private bool hasUnsavedChanges;
|
private bool hasUnsavedChanges;
|
||||||
private DispatcherTimer? _totpRefreshTimer;
|
private DispatcherTimer? _totpRefreshTimer;
|
||||||
private Dictionary<Guid, string> _totpCodes = new();
|
private Dictionary<Guid, string> _totpCodes = new();
|
||||||
@@ -25,6 +26,31 @@ public class UnlockedRepositoryViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<PassStoreEntryGroup> PasswordGroups
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (rootDirectory == null) return [];
|
||||||
|
return rootDirectory
|
||||||
|
.Where(entry => entry is PassStoreEntryGroup)
|
||||||
|
.Select(entry => (entry as PassStoreEntryGroup)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public PassStoreEntryGroup SelectedPasswordGroup
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return PasswordGroups.First(group => group == currentDirectory);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (PasswordGroups.Any(group => group == value))
|
||||||
|
{
|
||||||
|
ChangeDirectory(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasUnsavedChanges
|
public bool HasUnsavedChanges
|
||||||
{
|
{
|
||||||
get => hasUnsavedChanges;
|
get => hasUnsavedChanges;
|
||||||
@@ -39,6 +65,7 @@ public class UnlockedRepositoryViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
passStore = store;
|
passStore = store;
|
||||||
currentDirectory = directory;
|
currentDirectory = directory;
|
||||||
|
rootDirectory = (directory as PassStoreEntryGroup)?.Parent;
|
||||||
HasUnsavedChanges = false;
|
HasUnsavedChanges = false;
|
||||||
InitializeTotpCodes();
|
InitializeTotpCodes();
|
||||||
StartTotpRefreshTimer();
|
StartTotpRefreshTimer();
|
||||||
@@ -88,6 +115,19 @@ public class UnlockedRepositoryViewModel : ViewModelBase
|
|||||||
HasUnsavedChanges = false;
|
HasUnsavedChanges = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ChangeDirectory(PassStoreEntryGroup newDir)
|
||||||
|
{
|
||||||
|
if (newDir == currentDirectory)
|
||||||
|
return;
|
||||||
|
|
||||||
|
currentDirectory = newDir;
|
||||||
|
InitializeTotpCodes();
|
||||||
|
StartTotpRefreshTimer();
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(SelectedPasswordGroup));
|
||||||
|
OnPropertyChanged(nameof(Passwords));
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeTotpCodes()
|
private void InitializeTotpCodes()
|
||||||
{
|
{
|
||||||
_totpCodes.Clear();
|
_totpCodes.Clear();
|
||||||
@@ -102,6 +142,10 @@ public class UnlockedRepositoryViewModel : ViewModelBase
|
|||||||
// Calculate time until next TOTP period boundary
|
// Calculate time until next TOTP period boundary
|
||||||
int secondsUntilNextCode = CalculateSecondsUntilNextTotpRefresh();
|
int secondsUntilNextCode = CalculateSecondsUntilNextTotpRefresh();
|
||||||
|
|
||||||
|
if (_totpRefreshTimer != null)
|
||||||
|
{
|
||||||
|
_totpRefreshTimer.Stop();
|
||||||
|
}
|
||||||
_totpRefreshTimer = new DispatcherTimer
|
_totpRefreshTimer = new DispatcherTimer
|
||||||
{
|
{
|
||||||
Interval = TimeSpan.FromSeconds(secondsUntilNextCode)
|
Interval = TimeSpan.FromSeconds(secondsUntilNextCode)
|
||||||
|
|||||||
@@ -56,22 +56,23 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Рамочка -->
|
<!-- Группы паролей -->
|
||||||
<!-- <Border BorderBrush="Gray"
|
<TreeView x:Name="GroupTree"
|
||||||
BorderThickness="1"
|
ItemsSource="{Binding PasswordGroups}"
|
||||||
CornerRadius="5"
|
SelectedItem="{Binding SelectedPasswordGroup}"
|
||||||
Padding="20"
|
Background="#FFFFFFFF"
|
||||||
Background="#F5F5F5"
|
SelectionMode="Single" >
|
||||||
HorizontalAlignment="Left">
|
<TreeView.ItemTemplate>
|
||||||
|
<TreeDataTemplate ItemsSource="{Binding ChildGroups}">
|
||||||
|
<Border Background="Transparent"
|
||||||
|
DoubleTapped="Entry_DoubleTapped">
|
||||||
|
<TextBlock Text="{Binding DisplayName}"
|
||||||
|
Foreground="Black" />
|
||||||
|
</Border>
|
||||||
|
</TreeDataTemplate>
|
||||||
|
</TreeView.ItemTemplate>
|
||||||
|
</TreeView>
|
||||||
|
|
||||||
<StackPanel HorizontalAlignment="Left">
|
|
||||||
<Button Content="All Passwords"
|
|
||||||
Width="120"
|
|
||||||
Height="30"
|
|
||||||
HorizontalAlignment="Left"/>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
</Border> -->
|
|
||||||
<!-- Save Passwords -->
|
<!-- Save Passwords -->
|
||||||
<Button Content="Save Passwords"
|
<Button Content="Save Passwords"
|
||||||
Classes="accentSidebarButton"
|
Classes="accentSidebarButton"
|
||||||
|
|||||||
Reference in New Issue
Block a user