Entry history delete and restore work

This commit is contained in:
Geoffroy BONNEVILLE
2020-04-16 14:08:50 +02:00
parent 98ac418f62
commit 9befdc321a
15 changed files with 196 additions and 96 deletions

View File

@@ -89,6 +89,7 @@
<Compile Include="Common\Mappings\IMapFrom.cs" /> <Compile Include="Common\Mappings\IMapFrom.cs" />
<Compile Include="Common\Mappings\MappingProfile.cs" /> <Compile Include="Common\Mappings\MappingProfile.cs" />
<Compile Include="Entry\Commands\AddHistory\AddHistoryCommand.cs" /> <Compile Include="Entry\Commands\AddHistory\AddHistoryCommand.cs" />
<Compile Include="Entry\Commands\DeleteHistory\DeleteHistoryCommand.cs" />
<Compile Include="Entry\Commands\RestoreHistory\RestoreHistoryCommand.cs" /> <Compile Include="Entry\Commands\RestoreHistory\RestoreHistoryCommand.cs" />
<Compile Include="Entry\Queries\GetEntry\GetEntryQuery.cs" /> <Compile Include="Entry\Queries\GetEntry\GetEntryQuery.cs" />
<Compile Include="Group\Commands\DeleteEntry\DeleteEntryCommand.cs" /> <Compile Include="Group\Commands\DeleteEntry\DeleteEntryCommand.cs" />

View File

@@ -45,6 +45,7 @@ namespace ModernKeePass.Application.Common.Interfaces
void AddHistory(string entryId); void AddHistory(string entryId);
void RestoreFromHistory(string entryId, int historyIndex); void RestoreFromHistory(string entryId, int historyIndex);
void DeleteHistory(string entryId, int historyIndex);
IEnumerable<EntryEntity> Search(string groupId, string text); IEnumerable<EntryEntity> Search(string groupId, string text);
} }

View File

@@ -0,0 +1,29 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.DeleteHistory
{
public class DeleteHistoryCommand : IRequest
{
public string EntryId { get; set; }
public int HistoryIndex { get; set; }
public class DeleteHistoryCommandHandler : IRequestHandler<DeleteHistoryCommand>
{
private readonly IDatabaseProxy _database;
public DeleteHistoryCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(DeleteHistoryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.DeleteHistory(message.EntryId, message.HistoryIndex);
}
}
}
}

View File

@@ -28,7 +28,6 @@ namespace ModernKeePass.Application.Entry.Models
public bool HasExpirationDate { get; set; } public bool HasExpirationDate { get; set; }
public DateTimeOffset ExpirationDate { get; set; } public DateTimeOffset ExpirationDate { get; set; }
public DateTimeOffset ModificationDate { get; set; } public DateTimeOffset ModificationDate { get; set; }
public bool IsDirty { get; set; }
public override string ToString() public override string ToString()
{ {
@@ -47,7 +46,7 @@ namespace ModernKeePass.Application.Entry.Models
.ForMember(d => d.Url, opts => opts.MapFrom(s => s.Url)) .ForMember(d => d.Url, opts => opts.MapFrom(s => s.Url))
.ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Notes)) .ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Notes))
.ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s => s.AdditionalFields)) .ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s => s.AdditionalFields))
.ForMember(d => d.History, opts => opts.MapFrom(s => s.History.OrderByDescending(h => h.LastModificationDate))) .ForMember(d => d.History, opts => opts.MapFrom(s => s.History.Reverse()))
.ForMember(d => d.HasExpirationDate, opts => opts.MapFrom(s => s.HasExpirationDate)) .ForMember(d => d.HasExpirationDate, opts => opts.MapFrom(s => s.HasExpirationDate))
.ForMember(d => d.ExpirationDate, opts => opts.MapFrom(s => s.ExpirationDate)) .ForMember(d => d.ExpirationDate, opts => opts.MapFrom(s => s.ExpirationDate))
.ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationDate)) .ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationDate))

View File

@@ -9,7 +9,7 @@ using ModernKeePass.Domain.Interfaces;
namespace ModernKeePass.Application.Group.Models namespace ModernKeePass.Application.Group.Models
{ {
public class GroupVm: IEntityVm, ISelectableModel, IMapFrom<GroupEntity> public class GroupVm: IEntityVm, IMapFrom<GroupEntity>
{ {
public string ParentGroupId { get; set; } public string ParentGroupId { get; set; }
public string ParentGroupName { get; set; } public string ParentGroupName { get; set; }
@@ -18,7 +18,6 @@ namespace ModernKeePass.Application.Group.Models
public Icon Icon { get; set; } public Icon Icon { get; set; }
public List<GroupVm> SubGroups { get; set; } public List<GroupVm> SubGroups { get; set; }
public List<EntryVm> Entries { get; set; } public List<EntryVm> Entries { get; set; }
public bool IsSelected { get; set; }
public override string ToString() public override string ToString()
{ {

View File

@@ -266,6 +266,12 @@ namespace ModernKeePass.Infrastructure.KeePass
pwEntry.Touch(true); pwEntry.Touch(true);
} }
public void DeleteHistory(string entryId, int historyIndex)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.History.RemoveAt((uint)historyIndex);
}
public void UpdateGroup(string groupId) public void UpdateGroup(string groupId)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -258,4 +258,10 @@
<data name="NewImportFormatHelpCSV" xml:space="preserve"> <data name="NewImportFormatHelpCSV" xml:space="preserve">
<value>The CSV file needs to be formatted as such: Name of the account;Login;Password;URL;Comments</value> <value>The CSV file needs to be formatted as such: Name of the account;Login;Password;URL;Comments</value>
</data> </data>
<data name="HistoryDeleteDescription" xml:space="preserve">
<value>Deleting this history entry will not delete current entry.</value>
</data>
<data name="HistoryDeleteTitle" xml:space="preserve">
<value>Delete history entry ?</value>
</data>
</root> </root>

View File

@@ -258,4 +258,10 @@
<data name="NewImportFormatHelpCSV" xml:space="preserve"> <data name="NewImportFormatHelpCSV" xml:space="preserve">
<value>Le fichier CSV doit être formatté de la façon suivante: Nom du compte;Login;Mot de passe;URL;Commentaires</value> <value>Le fichier CSV doit être formatté de la façon suivante: Nom du compte;Login;Mot de passe;URL;Commentaires</value>
</data> </data>
<data name="HistoryDeleteDescription" xml:space="preserve">
<value>Supprimer cet historique ne supprimera pas l'entrée en cours.</value>
</data>
<data name="HistoryDeleteTitle" xml:space="preserve">
<value>Supprimer cet historique ?</value>
</data>
</root> </root>

View File

@@ -11,6 +11,7 @@ using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Models; using ModernKeePass.Application.Database.Models;
using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Entry.Commands.AddHistory; using ModernKeePass.Application.Entry.Commands.AddHistory;
using ModernKeePass.Application.Entry.Commands.DeleteHistory;
using ModernKeePass.Application.Entry.Commands.RestoreHistory; using ModernKeePass.Application.Entry.Commands.RestoreHistory;
using ModernKeePass.Application.Entry.Commands.SetFieldValue; using ModernKeePass.Application.Entry.Commands.SetFieldValue;
using ModernKeePass.Application.Entry.Models; using ModernKeePass.Application.Entry.Models;
@@ -44,17 +45,42 @@ namespace ModernKeePass.ViewModels
public bool SpecialPatternSelected { get; set; } public bool SpecialPatternSelected { get; set; }
public bool BracketsPatternSelected { get; set; } public bool BracketsPatternSelected { get; set; }
public string CustomChars { get; set; } = string.Empty; public string CustomChars { get; set; } = string.Empty;
public string Id => _entry.Id; public string Id => SelectedItem.Id;
public bool IsRecycleOnDelete
{
get
{
var database = Database;
return database.IsRecycleBinEnabled && _parent.Id != database.RecycleBinId;
}
}
public IEnumerable<GroupVm> BreadCrumb => new List<GroupVm> { _parent }; public IEnumerable<GroupVm> BreadCrumb => new List<GroupVm> { _parent };
public ObservableCollection<EntryVm> History { get; }
/// <summary> /// <summary>
/// Determines if the Entry is current or from history /// Determines if the Entry is current or from history
/// </summary> /// </summary>
public bool IsSelected public bool IsCurrentEntry => SelectedIndex == 0;
public EntryVm SelectedItem
{ {
get { return _isSelected; } get { return _selectedItem; }
set { SetProperty(ref _isSelected, value); } set
{
SetProperty(ref _selectedItem, value);
if (value != null) OnPropertyChanged();
}
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
SetProperty(ref _selectedIndex, value);
OnPropertyChanged(nameof(IsCurrentEntry));
}
} }
public double PasswordLength public double PasswordLength
@@ -65,26 +91,26 @@ namespace ModernKeePass.ViewModels
public string Title public string Title
{ {
get { return _entry.Title; } get { return SelectedItem.Title; }
set set
{ {
_entry.Title = value; SelectedItem.Title = value;
SetFieldValue(nameof(Title), value).Wait(); SetFieldValue(nameof(Title), value).Wait();
} }
} }
public string UserName public string UserName
{ {
get { return _entry.Username; } get { return SelectedItem.Username; }
set { _entry.Username = value; } set { SelectedItem.Username = value; }
} }
public string Password public string Password
{ {
get { return _entry.Password; } get { return SelectedItem.Password; }
set set
{ {
_entry.Password = value; SelectedItem.Password = value;
SetFieldValue(nameof(Password), value).Wait(); SetFieldValue(nameof(Password), value).Wait();
OnPropertyChanged(nameof(Password)); OnPropertyChanged(nameof(Password));
OnPropertyChanged(nameof(PasswordComplexityIndicator)); OnPropertyChanged(nameof(PasswordComplexityIndicator));
@@ -93,64 +119,64 @@ namespace ModernKeePass.ViewModels
public string Url public string Url
{ {
get { return _entry.Url?.ToString(); } get { return SelectedItem.Url?.ToString(); }
set set
{ {
_entry.Url = new Uri(value); SelectedItem.Url = new Uri(value);
SetFieldValue(nameof(Url), value).Wait(); SetFieldValue(nameof(Url), value).Wait();
} }
} }
public string Notes public string Notes
{ {
get { return _entry.Notes; } get { return SelectedItem.Notes; }
set set
{ {
_entry.Notes = value; SelectedItem.Notes = value;
SetFieldValue(nameof(Notes), value).Wait(); SetFieldValue(nameof(Notes), value).Wait();
} }
} }
public Symbol Icon public Symbol Icon
{ {
get { return (Symbol)Enum.Parse(typeof(Symbol), _entry.Icon.ToString()); } get { return (Symbol)Enum.Parse(typeof(Symbol), SelectedItem.Icon.ToString()); }
set set
{ {
_entry.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString()); SelectedItem.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString());
SetFieldValue(nameof(Icon), _entry.Icon).Wait(); SetFieldValue(nameof(Icon), SelectedItem.Icon).Wait();
} }
} }
public DateTimeOffset ExpiryDate public DateTimeOffset ExpiryDate
{ {
get { return _entry.ExpirationDate; } get { return SelectedItem.ExpirationDate; }
set set
{ {
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
_entry.ExpirationDate = value.Date; SelectedItem.ExpirationDate = value.Date;
SetFieldValue("ExpirationDate", _entry.ExpirationDate).Wait(); SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait();
} }
} }
public TimeSpan ExpiryTime public TimeSpan ExpiryTime
{ {
get { return _entry.ExpirationDate.TimeOfDay; } get { return SelectedItem.ExpirationDate.TimeOfDay; }
set set
{ {
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
_entry.ExpirationDate = _entry.ExpirationDate.Date.Add(value); SelectedItem.ExpirationDate = SelectedItem.ExpirationDate.Date.Add(value);
SetFieldValue("ExpirationDate", _entry.ExpirationDate).Wait(); SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait();
} }
} }
public bool HasExpirationDate public bool HasExpirationDate
{ {
get { return _entry.HasExpirationDate; } get { return SelectedItem.HasExpirationDate; }
set set
{ {
_entry.HasExpirationDate = value; SelectedItem.HasExpirationDate = value;
SetFieldValue(nameof(HasExpirationDate), value).Wait(); SetFieldValue(nameof(HasExpirationDate), value).Wait();
OnPropertyChanged(nameof(HasExpirationDate)); OnPropertyChanged(nameof(HasExpirationDate));
} }
@@ -158,29 +184,28 @@ namespace ModernKeePass.ViewModels
public SolidColorBrush BackgroundColor public SolidColorBrush BackgroundColor
{ {
get { return _entry?.BackgroundColor.ToSolidColorBrush(); } get { return SelectedItem?.BackgroundColor.ToSolidColorBrush(); }
set set
{ {
_entry.BackgroundColor = value.ToColor(); SelectedItem.BackgroundColor = value.ToColor();
SetFieldValue(nameof(BackgroundColor), _entry.BackgroundColor).Wait(); SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor).Wait();
} }
} }
public SolidColorBrush ForegroundColor public SolidColorBrush ForegroundColor
{ {
get { return _entry?.ForegroundColor.ToSolidColorBrush(); } get { return SelectedItem?.ForegroundColor.ToSolidColorBrush(); }
set set
{ {
_entry.ForegroundColor = value.ToColor(); SelectedItem.ForegroundColor = value.ToColor();
SetFieldValue(nameof(ForegroundColor), _entry.ForegroundColor).Wait(); SetFieldValue(nameof(ForegroundColor), SelectedItem.ForegroundColor).Wait();
} }
} }
public ObservableCollection<EntryVm> History { get; }
public bool IsEditMode public bool IsEditMode
{ {
get { return IsSelected && _isEditMode; } get { return IsCurrentEntry && _isEditMode; }
set { SetProperty(ref _isEditMode, value); } set { SetProperty(ref _isEditMode, value); }
} }
@@ -199,11 +224,12 @@ namespace ModernKeePass.ViewModels
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly GroupVm _parent; private readonly GroupVm _parent;
private EntryVm _entry; private EntryVm _selectedItem;
private int _selectedIndex;
private bool _isEditMode; private bool _isEditMode;
private bool _isRevealPassword; private bool _isRevealPassword;
private double _passwordLength = 25; private double _passwordLength = 25;
private bool _isSelected; private bool _isDirty;
public EntryDetailVm() { } public EntryDetailVm() { }
@@ -212,14 +238,14 @@ namespace ModernKeePass.ViewModels
public EntryDetailVm(string entryId, IMediator mediator) public EntryDetailVm(string entryId, IMediator mediator)
{ {
_mediator = mediator; _mediator = mediator;
_entry = _mediator.Send(new GetEntryQuery { Id = entryId }).GetAwaiter().GetResult(); SelectedItem = _mediator.Send(new GetEntryQuery { Id = entryId }).GetAwaiter().GetResult();
_parent = _mediator.Send(new GetGroupQuery { Id = _entry.ParentGroupId }).GetAwaiter().GetResult(); _parent = _mediator.Send(new GetGroupQuery { Id = SelectedItem.ParentGroupId }).GetAwaiter().GetResult();
History = new ObservableCollection<EntryVm> {_entry}; History = new ObservableCollection<EntryVm> { SelectedItem };
foreach (var entry in _entry.History) foreach (var entry in SelectedItem.History)
{ {
History.Add(entry); History.Add(entry);
} }
IsSelected = true; SelectedIndex = 0;
SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty); SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty);
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword()); GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
@@ -247,41 +273,41 @@ namespace ModernKeePass.ViewModels
public async Task MarkForDelete(string recycleBinTitle) public async Task MarkForDelete(string recycleBinTitle)
{ {
await _mediator.Send(new DeleteEntryCommand {EntryId = Id, ParentGroupId = _entry.ParentGroupId, RecycleBinName = recycleBinTitle}); await _mediator.Send(new DeleteEntryCommand {EntryId = Id, ParentGroupId = SelectedItem.ParentGroupId, RecycleBinName = recycleBinTitle});
} }
public async Task Move(GroupVm destination) public async Task Move(GroupVm destination)
{ {
await _mediator.Send(new AddEntryCommand { ParentGroup = destination, Entry = _entry }); await _mediator.Send(new AddEntryCommand { ParentGroup = destination, Entry = SelectedItem });
await _mediator.Send(new RemoveEntryCommand { ParentGroup = _parent, Entry = _entry }); await _mediator.Send(new RemoveEntryCommand { ParentGroup = _parent, Entry = SelectedItem });
} }
public async Task SetFieldValue(string fieldName, object value) public async Task SetFieldValue(string fieldName, object value)
{ {
await _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = fieldName, FieldValue = value }); await _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = fieldName, FieldValue = value });
((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); ((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
_entry.IsDirty = true; _isDirty = true;
} }
public async Task AddHistory() public async Task AddHistory()
{ {
if (_entry.IsDirty) await _mediator.Send(new AddHistoryCommand { EntryId = Id }); if (_isDirty) await _mediator.Send(new AddHistoryCommand { EntryId = Id });
} }
internal void SetEntry(EntryVm entry, int index)
{
_entry = entry;
IsSelected = index == 0;
OnPropertyChanged();
}
private async Task RestoreHistory() private async Task RestoreHistory()
{ {
var index = History.IndexOf(_entry); await _mediator.Send(new RestoreHistoryCommand { EntryId = Id, HistoryIndex = History.Count - SelectedIndex - 1 });
var entryToRestore = History[index]; History.Insert(0, SelectedItem);
SetEntry(entryToRestore, 0); SelectedIndex = 0;
await _mediator.Send(new RestoreHistoryCommand { EntryId = Id, HistoryIndex = index }); ((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
History.Insert(0, entryToRestore); }
public async Task DeleteHistory()
{
await _mediator.Send(new DeleteHistoryCommand { EntryId = Id, HistoryIndex = History.Count - SelectedIndex - 1 });
History.RemoveAt(SelectedIndex);
SelectedIndex = 0;
((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
} }
private async Task SaveChanges() private async Task SaveChanges()
@@ -289,7 +315,7 @@ namespace ModernKeePass.ViewModels
await AddHistory(); await AddHistory();
await _mediator.Send(new SaveDatabaseCommand()); await _mediator.Send(new SaveDatabaseCommand());
((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); ((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
_entry.IsDirty = false; _isDirty = false;
} }
} }
} }

View File

@@ -1,6 +1,5 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;

View File

@@ -388,7 +388,11 @@
<ColumnDefinition Width="{StaticResource MenuGridLength}" x:Name="LeftListViewColumn" /> <ColumnDefinition Width="{StaticResource MenuGridLength}" x:Name="LeftListViewColumn" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl x:Uid="HistoryLeftListView" ItemsSource="{Binding History}" ResizeTarget="{Binding ElementName=LeftListViewColumn}" SelectionChanged="HamburgerMenuUserControl_OnSelectionChanged" /> <userControls:HamburgerMenuUserControl x:Uid="HistoryLeftListView"
ItemsSource="{Binding History}"
ResizeTarget="{Binding ElementName=LeftListViewColumn}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20,0,0,20"> <StackPanel Margin="20,0,0,20">
@@ -409,7 +413,7 @@
</Style> </Style>
</StackPanel.Resources> </StackPanel.Resources>
<TextBlock x:Uid="EntryLogin" /> <TextBlock x:Uid="EntryLogin" />
<local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource EntryTextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsSelected}"> <local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource EntryTextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick"> <core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding UserName}" /> <actions:ClipboardAction Text="{Binding UserName}" />
@@ -418,8 +422,8 @@
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</local:TextBoxWithButton> </local:TextBoxWithButton>
<TextBlock x:Uid="EntryPassword" /> <TextBlock x:Uid="EntryPassword" />
<PasswordBox HorizontalAlignment="Left" Password="{Binding Password, Mode=TwoWay}" Width="350" Height="32" IsPasswordRevealButtonEnabled="True" Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource PasswordBoxWithButtonStyle}" IsEnabled="{Binding IsSelected}" /> <PasswordBox HorizontalAlignment="Left" Password="{Binding Password, Mode=TwoWay}" Width="350" Height="32" IsPasswordRevealButtonEnabled="True" Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource PasswordBoxWithButtonStyle}" IsEnabled="{Binding IsCurrentEntry}" />
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource EntryTextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsSelected}"> <local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource EntryTextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick"> <core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding Password}" /> <actions:ClipboardAction Text="{Binding Password}" />
@@ -430,7 +434,7 @@
<ProgressBar Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Maximum="128" Width="350" HorizontalAlignment="Left" Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" /> <ProgressBar Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Maximum="128" Width="350" HorizontalAlignment="Left" Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" />
<CheckBox x:Uid="EntryShowPassword" HorizontalAlignment="Left" Margin="-3,0,0,0" IsChecked="{Binding IsRevealPassword, Mode=TwoWay}" IsEnabled="{Binding IsRevealPasswordEnabled}" /> <CheckBox x:Uid="EntryShowPassword" HorizontalAlignment="Left" Margin="-3,0,0,0" IsChecked="{Binding IsRevealPassword, Mode=TwoWay}" IsEnabled="{Binding IsRevealPasswordEnabled}" />
<TextBlock TextWrapping="Wrap" Text="URL" FontSize="18"/> <TextBlock TextWrapping="Wrap" Text="URL" FontSize="18"/>
<local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" MaxLength="256" Style="{StaticResource EntryTextBoxWithButtonStyle}" ButtonSymbol="&#xE111;" IsEnabled="{Binding IsSelected}"> <local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" MaxLength="256" Style="{StaticResource EntryTextBoxWithButtonStyle}" ButtonSymbol="&#xE111;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick"> <core:EventTriggerBehavior EventName="ButtonClick">
<actions:NavigateToUrlAction Url="{Binding Url}" /> <actions:NavigateToUrlAction Url="{Binding Url}" />
@@ -438,8 +442,8 @@
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</local:TextBoxWithButton> </local:TextBoxWithButton>
<TextBlock x:Uid="EntryNotes" /> <TextBlock x:Uid="EntryNotes" />
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsSelected}" /> <TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" />
<CheckBox x:Uid="EntryExpirationDate" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsSelected}" /> <CheckBox x:Uid="EntryExpirationDate" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -458,11 +462,11 @@
<StackPanel x:Name="EditDesign" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Orientation="Horizontal"> <StackPanel x:Name="EditDesign" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Orientation="Horizontal">
<StackPanel Width="250" HorizontalAlignment="Left"> <StackPanel Width="250" HorizontalAlignment="Left">
<TextBlock x:Uid="EntryBackgroundColor" /> <TextBlock x:Uid="EntryBackgroundColor" />
<userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsSelected}" /> <userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</StackPanel> </StackPanel>
<StackPanel Width="250" HorizontalAlignment="Left"> <StackPanel Width="250" HorizontalAlignment="Left">
<TextBlock x:Uid="EntryForegroundColor" /> <TextBlock x:Uid="EntryForegroundColor" />
<userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsSelected}" /> <userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
@@ -529,21 +533,16 @@
<userControls:TopMenuUserControl <userControls:TopMenuUserControl
x:Name="TopMenu" Grid.Column="2" x:Name="TopMenu" Grid.Column="2"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
MoveButtonVisibility="{Binding IsSelected, Converter={StaticResource BooleanToVisibilityConverter}}" MoveButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource BooleanToVisibilityConverter}}"
RestoreButtonVisibility="{Binding IsSelected, Converter={StaticResource InverseBooleanToVisibilityConverter}}" RestoreButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource InverseBooleanToVisibilityConverter}}"
SaveCommand="{Binding SaveCommand}" SaveCommand="{Binding SaveCommand}"
MoveCommand="{Binding MoveCommand}" MoveCommand="{Binding MoveCommand}"
RestoreCommand="{Binding RestoreCommand}"> RestoreCommand="{Binding RestoreCommand}"
DeleteButtonClick="TopMenu_OnDeleteButtonClick">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="EditButtonClick"> <core:EventTriggerBehavior EventName="EditButtonClick">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" /> <actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RestoreButtonClick">
<core:ChangePropertyAction TargetObject="" />
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="DeleteButtonClick">
<actions:DeleteEntityAction Entity="{Binding}" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="MoveButtonClick"> <core:EventTriggerBehavior EventName="MoveButtonClick">
<core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" /> <core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
<!--<actions:ToastAction x:Uid="RestoreEntryCommand" Title="{Binding Title}" />--> <!--<actions:ToastAction x:Uid="RestoreEntryCommand" Title="{Binding Title}" />-->

View File

@@ -1,5 +1,4 @@
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
using ModernKeePass.Common; using ModernKeePass.Common;
using ModernKeePass.Models; using ModernKeePass.Models;
@@ -22,7 +21,7 @@ namespace ModernKeePass.Views
/// gestion de la durée de vie des processus /// gestion de la durée de vie des processus
/// </summary> /// </summary>
public NavigationHelper NavigationHelper { get; } public NavigationHelper NavigationHelper { get; }
public EntryDetailPage() public EntryDetailPage()
{ {
InitializeComponent(); InitializeComponent();
@@ -65,19 +64,35 @@ namespace ModernKeePass.Views
VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true); VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true);
} }
private void HamburgerMenuUserControl_OnSelectionChanged(object sender, SelectionChangedEventArgs e) private async void TopMenu_OnDeleteButtonClick(object sender, RoutedEventArgs e)
{ {
var listView = sender as ListView; var resource = new ResourceHelper();
if (listView == null) return; if (Model.IsCurrentEntry)
var index = listView.SelectedIndex;
switch (index)
{ {
case -1: var isRecycleOnDelete = Model.IsRecycleOnDelete;
return;
default: var message = isRecycleOnDelete
var entry = listView.SelectedItem as Application.Entry.Models.EntryVm; ? resource.GetResourceValue("EntryRecyclingConfirmation")
Model.SetEntry(entry, index); : resource.GetResourceValue("EntryDeletingConfirmation");
break; await MessageDialogHelper.ShowActionDialog(resource.GetResourceValue("EntityDeleteTitle"), message,
resource.GetResourceValue("EntityDeleteActionButton"),
resource.GetResourceValue("EntityDeleteCancelButton"), async a =>
{
var text = isRecycleOnDelete ? resource.GetResourceValue("EntryRecycled") : resource.GetResourceValue("EntryDeleted");
//ToastNotificationHelper.ShowMovedToast(Entity, _resource.GetResourceValue("EntityDeleting"), text);
await Model.MarkForDelete(resource.GetResourceValue("RecycleBinTitle"));
NavigationHelper.GoBack();
}, null);
}
else
{
await MessageDialogHelper.ShowActionDialog(resource.GetResourceValue("HistoryDeleteTitle"), resource.GetResourceValue("HistoryDeleteDescription"),
resource.GetResourceValue("EntityDeleteActionButton"),
resource.GetResourceValue("EntityDeleteCancelButton"), async a =>
{
//ToastNotificationHelper.ShowMovedToast(Entity, _resource.GetResourceValue("EntityDeleting"), text);
await Model.DeleteHistory();
}, null);
} }
} }
} }

View File

@@ -15,7 +15,8 @@
<ListView <ListView
ItemsSource="{Binding ItemsSource, ElementName=UserControl}" ItemsSource="{Binding ItemsSource, ElementName=UserControl}"
SelectionChanged="Selector_OnSelectionChanged" SelectionChanged="Selector_OnSelectionChanged"
SelectedItem="{Binding SelectedItem, ElementName=UserControl}" SelectedItem="{Binding SelectedItem, ElementName=UserControl, Mode=TwoWay}"
SelectedIndex="{Binding SelectedIndex, ElementName=UserControl, Mode=TwoWay}"
IsSwipeEnabled="false" IsSwipeEnabled="false"
IsSynchronizedWithCurrentItem="False" IsSynchronizedWithCurrentItem="False"
RequestedTheme="Dark" RequestedTheme="Dark"

View File

@@ -100,6 +100,18 @@ namespace ModernKeePass.Views.UserControls
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register(
nameof(SelectedIndex),
typeof(int),
typeof(HamburgerMenuUserControl),
new PropertyMetadata(-1, (o, args) => { }));
public bool IsOpen public bool IsOpen
{ {
get { return (bool)GetValue(IsOpenProperty); } get { return (bool)GetValue(IsOpenProperty); }

View File

@@ -1,4 +1,5 @@
Database corruption issues should now be a thing of the past ! Database corruption issues should now be a thing of the past !
Added the ability to move entries and groups Added the ability to move entries and groups
Edits are now in a popup instead of inline Edits are now in a popup instead of inline
Allows restoring and deleting from entry history
Updated KeePass lib to version 2.44 Updated KeePass lib to version 2.44