Skip to content
This repository has been archived by the owner on Feb 13, 2024. It is now read-only.

Implement Undo functionality #4078

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
using System.Collections.Generic;
using System.Linq;
using Xunit;
using static TogglDesktop.Toggl;

namespace TogglDesktop.Tests
{
public class LibraryIntegrationTests : IClassFixture<LibraryFixture>
{
private readonly LibraryFixture _state;
private readonly TogglTimeEntryView _timeEntrySnapshot;
private readonly string _firstTimeEntryGuid;

public LibraryIntegrationTests(LibraryFixture libraryFixture)
{
_state = libraryFixture;
Assert.True(Toggl.SetLoggedInUser(_state.MeJson));
_timeEntrySnapshot = _state.TimeEntries[0];
_firstTimeEntryGuid = _state.TimeEntries[0].GUID;
}

Expand Down Expand Up @@ -148,9 +151,9 @@ public void ContinueLatestShouldStartTimeEntry()
[Fact]
public void DeleteTimeEntryShouldDeleteTimeEntry()
{
Toggl.DeleteTimeEntry(_firstTimeEntryGuid);
Toggl.DeleteTimeEntry(_timeEntrySnapshot);

Assert.DoesNotContain(_state.TimeEntries, te => te.GUID == _firstTimeEntryGuid);
Assert.DoesNotContain(_state.TimeEntries, te => te.GUID == _timeEntrySnapshot.GUID);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using static TogglDesktop.Toggl;

namespace TogglDesktop.Services
{
public class UndoDeletionService
{
private Stack<TogglTimeEntryView> undoStack;
public UndoDeletionService()
{
undoStack = new Stack<TogglTimeEntryView>();
}

public bool CanUndo => undoStack.Any();

public void SnapshotTimeEntry(TogglTimeEntryView timeEntry)
{
undoStack.Push(timeEntry);
}

public void UndoDeletion()
{
if (CanUndo)
{
var te = undoStack.Pop();
Toggl.StartWithCurrentRunning(te.Description, te.Duration, te.TID, te.PID, "", te.Tags, true, te.Started, te.Ended, false);
if (te.Billable)
Toggl.SetTimeEntryBillable(te.GUID, te.Billable);
}
}
}
}
47 changes: 38 additions & 9 deletions src/ui/windows/TogglDesktop/TogglDesktop/Toggl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@

namespace TogglDesktop
{
public static partial class Toggl
public static partial class Toggl
{
#region constants and static fields
#region constants and static fields

public static UndoDeletionService UndoDeletionService = new UndoDeletionService();
public const string Project = "project";
public const string Duration = "duration";
public const string Description = "description";
Expand Down Expand Up @@ -293,14 +294,15 @@ public static bool ContinueLatest(bool preventOnApp = false)
return toggl_continue_latest(ctx, preventOnApp);
}

public static bool DeleteTimeEntry(string guid)
public static bool DeleteTimeEntry(TogglTimeEntryView timeEntry)
{
return toggl_delete_time_entry(ctx, guid);
Toggl.UndoDeletionService.SnapshotTimeEntry(timeEntry);
return toggl_delete_time_entry(ctx, timeEntry.GUID);
}

#region changing time entry
#region changing time entry

public static bool SetTimeEntryDuration(string guid, string value)
public static bool SetTimeEntryDuration(string guid, string value)
{
using (Performance.Measure("changing time entry duration"))
{
Expand Down Expand Up @@ -514,7 +516,34 @@ public static bool SetLoggedInUser(string json) {
return testing_set_logged_in_user(ctx, json);
}

public static string Start(
public static string StartWithCurrentRunning(
string description,
string duration,
UInt64 task_id,
UInt64 project_id,
string project_guid,
string tags,
bool preventOnApp,
ulong started,
ulong ended,
bool stop_current_running)
{
OnUserTimeEntryStart();

return toggl_start_with_current_running(ctx,
description,
duration,
task_id,
project_id,
project_guid,
tags,
preventOnApp,
started,
ended,
stop_current_running);
}

public static string Start(
string description,
string duration,
UInt64 task_id,
Expand Down Expand Up @@ -1422,14 +1451,14 @@ public static void ShowErrorAndNotify(string errmsg, Exception ex)
BugsnagService.NotifyBugsnag(ex);
}

public static bool AskToDeleteEntry(string guid)
public static bool AskToDeleteEntry(TogglTimeEntryView timeEntry)
{
var result = MessageBox.Show(mainWindow, "Deleted time entries cannot be restored.", "Delete time entry?",
MessageBoxButton.OKCancel, "Delete entry");

if (result == MessageBoxResult.OK)
{
return DeleteTimeEntry(guid);
return DeleteTimeEntry(timeEntry);
}
return false;
}
Expand Down
20 changes: 20 additions & 0 deletions src/ui/windows/TogglDesktop/TogglDesktop/TogglApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,26 @@ private static extern bool toggl_logout(
private static extern bool toggl_clear_cache(
IntPtr context);

// returns GUID of the started time entry. you must free() the result
[DllImport(dll, CharSet = charset, CallingConvention = convention)]
private static extern string toggl_start_with_current_running(
IntPtr context,
[MarshalAs(UnmanagedType.LPWStr)]
string description,
[MarshalAs(UnmanagedType.LPWStr)]
string duration,
UInt64 task_id,
UInt64 project_id,
[MarshalAs(UnmanagedType.LPWStr)]
string project_guid,
[MarshalAs(UnmanagedType.LPWStr)]
string tags,
[MarshalAs(UnmanagedType.I1)]
bool prevent_on_app,
UInt64 started,
UInt64 ended,
bool stop_current_running);

// returns GUID of the started time entry. you must free() the result
[DllImport(dll, CharSet = charset, CallingConvention = convention)]
private static extern string toggl_start(
Expand Down
2 changes: 2 additions & 0 deletions src/ui/windows/TogglDesktop/TogglDesktop/TogglDesktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@
<Compile Include="Diagnostics\IPerformanceToken.cs" />
<Compile Include="Diagnostics\Performance.cs" />
<Compile Include="Services\NsisPackageExtractor.cs" />
<Compile Include="Services\UndoDeletionService.cs" />
<Compile Include="Services\UpdateService.cs" />
<Compile Include="Services\UpdateStatus.cs" />
<Compile Include="Services\Win10\RunOnStartup.cs" />
Expand Down Expand Up @@ -783,6 +784,7 @@
<Resource Include="Resources\spider.png" />
<Resource Include="Resources\stopwatch.png" />
</ItemGroup>
<ItemGroup />
<Import Project="Dependencies.targets" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public static void DeleteTimeEntry(this TimeEntryCellViewModel cell)
{
if (cell.ConfirmlessDelete())
{
Toggl.DeleteTimeEntry(cell.Guid);
Toggl.DeleteTimeEntry(cell.TimeEntrySnapshot);
return;
}
Toggl.AskToDeleteEntry(cell.Guid);
Toggl.AskToDeleteEntry(cell.TimeEntrySnapshot);
}

private static bool IsDurationLessThan15Seconds(long durationInSeconds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<MenuItem Header="Delete time entry"
Command="{x:Static toggl:TimeEntryCellContextMenuCommands.DeleteCommand}"
CommandParameter="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="Undo Delete time entry"
Command="{x:Static toggl:TimeEntryCellContextMenuCommands.UndoDeletionCommand}"/>
<Separator />
<MenuItem Header="Collapse all days"
Command="{x:Static toggl:TimeEntryCellContextMenuCommands.CollapseAllDaysCommand}"/>
Expand All @@ -19,6 +21,9 @@
</ContextMenu>

<ContextMenu x:Key="TimeEntryHeaderContextMenu">
<MenuItem Header="Undo Delete time entry"
Command="{x:Static toggl:TimeEntryCellContextMenuCommands.UndoDeletionCommand}"/>
<Separator />
<MenuItem Header="Collapse all days"
Command="{x:Static toggl:TimeEntryCellContextMenuCommands.CollapseAllDaysCommand}"/>
<MenuItem Header="Expand all days"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using static TogglDesktop.Toggl;

namespace TogglDesktop.ViewModels
{
Expand All @@ -8,7 +9,7 @@ public class TimeEntryCellViewModel : ReactiveObject
public TimeEntryCellViewModel()
{
}

public TogglTimeEntryView TimeEntrySnapshot { get; set; }
[Reactive]
public TimeEntryLabelViewModel TimeEntryLabel { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Windows.Input;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;

Expand All @@ -11,8 +13,13 @@ public TimeEntryListViewModel()
this.WhenAnyValue(x => x.IsLoading)
.Select(x => !x)
.ToPropertyEx(this, x => x.IsViewEnabled);

UndoCommand = ReactiveCommand.Create(OnUndo);
}

public void OnUndo() =>
Toggl.UndoDeletionService.UndoDeletion();
public ReactiveCommand<Unit, Unit> UndoCommand { get; set; }
[Reactive]
public bool ShowLoadMore { get; private set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public TimeEntryCell()

public void Display(Toggl.TogglTimeEntryView item)
{
ViewModel.TimeEntrySnapshot = item;
ViewModel.Guid = item.GUID;
ViewModel.IsGroup = item.Group;
if (ViewModel.IsGroup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ static TimeEntryCellContextMenuCommands ()
{
var bindings = new[] {
new CommandBinding(DeleteCommand, deleteTimeEntry, canDeleteTimeEntry),
new CommandBinding(UndoDeletionCommand, undoDeletion, canUndoDeletion),
new CommandBinding(CollapseAllDaysCommand, collapseAllDays, canCollapseAllDays),
new CommandBinding(ExpandAllDaysCommand, expandAllDays, canExpandAllDays),
};
Expand Down Expand Up @@ -41,6 +42,23 @@ private static void deleteTimeEntry(object sender, ExecutedRoutedEventArgs e)

#endregion

#region undo delete time entry

public static readonly RoutedUICommand UndoDeletionCommand =
new RoutedUICommand("", "UndoDeletionCommand", typeof(TimeEntryCellContextMenuCommands));

private static void canUndoDeletion(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = Toggl.UndoDeletionService.CanUndo;
}

private static void undoDeletion(object sender, ExecutedRoutedEventArgs e)
{
Toggl.UndoDeletionService.UndoDeletion();
}

#endregion

#region collapse all days

public static readonly RoutedUICommand CollapseAllDaysCommand =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:toggl="clr-namespace:TogglDesktop"
xmlns:viewModels="clr-namespace:TogglDesktop.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Expand Down Expand Up @@ -59,6 +60,7 @@
</UserControl.CommandBindings>

<UserControl.InputBindings>
<KeyBinding Key="Z" Modifiers="Ctrl" Command="{Binding UndoCommand}"/>
<KeyBinding Key="Down" Command="{StaticResource HighlightDown}" />
<KeyBinding Key="Up" Command="{StaticResource HighlightUp}" />
<KeyBinding Key="Tab" Command="{StaticResource HighlightDown}" />
Expand All @@ -77,7 +79,12 @@
<KeyBinding Key="Right" Modifiers="Control" Command="{StaticResource ExpandAllDays}" />
<KeyBinding Key="Left" Modifiers="Control" Command="{StaticResource CollapseAllDays}" />
</UserControl.InputBindings>

<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Undo Delete time entry"
Command="{x:Static toggl:TimeEntryCellContextMenuCommands.UndoDeletionCommand}"/>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -850,10 +850,10 @@ private void deleteButton_OnClick(object sender, RoutedEventArgs e)
{
if (this.timeEntry.ConfirmlessDelete())
{
Toggl.DeleteTimeEntry(this.timeEntry.GUID);
Toggl.DeleteTimeEntry(this.timeEntry);
return;
}
Toggl.AskToDeleteEntry(this.timeEntry.GUID);
Toggl.AskToDeleteEntry(this.timeEntry);
}

private void clearUndoHistory()
Expand Down