Skip to content
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
1 change: 1 addition & 0 deletions HistoricalReactiveCommand/HistoricalReactiveCommand.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="ReactiveUI" Version="14.1.1" />
</ItemGroup>

Expand Down
34 changes: 33 additions & 1 deletion HistoricalReactiveCommand/History.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,27 @@ public class History : IHistory, IDisposable
{
private Stack<HistoryEntry> StackRedo { get; } = new Stack<HistoryEntry>();
private Stack<HistoryEntry> StackUndo { get; } = new Stack<HistoryEntry>();


public HistoryEntry LastCommand()
{
return StackUndo.Peek();
}

private readonly Subject<bool> _canRecord = new Subject<bool>();
private readonly Subject<bool> _canUndo = new Subject<bool>();
private readonly Subject<bool> _canRedo = new Subject<bool>();
private readonly Subject<bool> _canClear = new Subject<bool>();

/// <summary>
/// Время последней добавленной команды
/// </summary>
/// <remarks>Время именно добавленной команды, а не из стека</remarks>
public DateTime LastCommandTime { get; set; }
/// <summary>
/// Можно ли добавлять вложенные команды в стек
/// </summary>
public bool CanAddInternalCommand { get; set; } = true;

public History(string id)
{
Id = id;
Expand All @@ -40,6 +55,7 @@ public IObservable<HistoryEntry> Undo(Func<HistoryEntry, IObservable<HistoryEntr
{
StackRedo.Push(entry);
UpdateSubjects();
CanAddInternalCommand = true;
});
}

Expand All @@ -53,6 +69,7 @@ public IObservable<HistoryEntry> Redo(Func<HistoryEntry, IObservable<HistoryEntr
{
StackUndo.Push(entry);
UpdateSubjects();
CanAddInternalCommand = true;
});
}

Expand All @@ -63,6 +80,7 @@ public IObservable<HistoryEntry> Record(HistoryEntry entry, Func<HistoryEntry, I
return execute(entry).Do(updatedEntry =>
{
StackUndo.Push(updatedEntry);
LastCommandTime = entry.CreationtTime;
UpdateSubjects();
});
}
Expand All @@ -76,6 +94,20 @@ public IObservable<Unit> Clear()
return Observables.Unit;
}

public void Add(HistoryEntry entry)
{
StackRedo.Clear();
UpdateSubjects(true);
StackUndo.Push(entry);
LastCommandTime = entry.CreationtTime;
UpdateSubjects();
}

public void UndoPop()
{
StackUndo.Pop();
}

public void Dispose()
{
StackRedo.Clear();
Expand Down
32 changes: 30 additions & 2 deletions HistoricalReactiveCommand/HistoryContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,23 @@ internal HistoryContext(IHistory history, IScheduler outputScheduler)
.CombineLatest(history.CanUndo, (recordable, executable) => recordable && executable);

Undo = ReactiveCommand.CreateFromObservable<Unit, HistoryEntry>(
unit => history.Undo(entry => ResolveCommand(entry).Discard(entry)),
unit => history.Undo(entry =>
{
history.CanAddInternalCommand = false;
return ResolveCommand(entry).Discard(entry);
}),
CanUndo,
outputScheduler);

var CanRedo = CanRecord
.CombineLatest(history.CanRedo, (recordable, executable) => recordable && executable);

Redo = ReactiveCommand.CreateFromObservable<Unit, HistoryEntry>(
unit => history.Redo(entry => ResolveCommand(entry).Execute(entry)),
unit => history.Redo(entry =>
{
history.CanAddInternalCommand = false;
return ResolveCommand(entry).Execute(entry);
}),
CanRedo,
outputScheduler);

Expand All @@ -82,6 +90,13 @@ internal HistoryContext(IHistory history, IScheduler outputScheduler)

public IObservable<bool> CanRecord => _history.CanRecord;

public HistoryEntry LastCommand()
{
return _history.LastCommand();
}

public DateTime LastCommandTime => _history.LastCommandTime;

public ReactiveCommand<Unit, HistoryEntry> Undo { get; }

public ReactiveCommand<Unit, HistoryEntry> Redo { get; }
Expand Down Expand Up @@ -109,5 +124,18 @@ private static IReactiveCommandWithHistory ResolveCommand(HistoryEntry entry)
}

internal IObservable<HistoryEntry> Record(HistoryEntry entry, Func<HistoryEntry, IObservable<HistoryEntry>> execute) => _history.Record(entry, execute);

internal void Record(HistoryEntry entry)
{
if (_history.CanAddInternalCommand)
{
_history.Add(entry);
}
}

/// <summary>
/// Удление верхнего элемента
/// </summary>
public void UndoPop() => _history.UndoPop();
}
}
7 changes: 6 additions & 1 deletion HistoricalReactiveCommand/HistoryEntry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace HistoricalReactiveCommand
using System;
using System.Data;

namespace HistoricalReactiveCommand
{
public class HistoryEntry
{
Expand All @@ -7,11 +10,13 @@ public HistoryEntry(object parameter, object result, string commandKey)
Parameter = parameter;
Result = result;
CommandKey = commandKey;
CreationtTime = DateTime.UtcNow;
}

public object Parameter { get; set; }
public object Result { get; set; }
public string CommandKey { get; set; }
public DateTime CreationtTime { get; }
}
}

19 changes: 19 additions & 0 deletions HistoricalReactiveCommand/IHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,29 @@ public interface IHistory
IObservable<bool> CanRedo { get; }
IObservable<bool> CanRecord { get; }
IObservable<bool> CanClear { get; }
/// <summary>
/// Можно ли вызывать команды внутри команд
/// </summary>
bool CanAddInternalCommand { get; set; }
/// <summary>
/// Время последней добавленной команды
/// </summary>
/// <remarks>Время именно добавленной команды, а не из стека</remarks>
DateTime LastCommandTime { get; set; }

/// <summary>
/// Последняя команда
/// </summary>
HistoryEntry LastCommand();

IObservable<HistoryEntry> Undo(Func<HistoryEntry, IObservable<HistoryEntry>> discard);
IObservable<HistoryEntry> Redo(Func<HistoryEntry, IObservable<HistoryEntry>> execute);
IObservable<HistoryEntry> Record(HistoryEntry entry, Func<HistoryEntry, IObservable<HistoryEntry>> execute);
void Add(HistoryEntry entry);
/// <summary>
/// Удаление верхнего элемента
/// </summary>
void UndoPop();
IObservable<Unit> Clear();
}
}
2 changes: 2 additions & 0 deletions HistoricalReactiveCommand/IReactiveCommandWithHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ public interface IReactiveCommandWithHistory : IReactiveCommand
{
IObservable<HistoryEntry> Execute(HistoryEntry entry);
IObservable<HistoryEntry> Discard(HistoryEntry entry);
void AddHistoryRecord(dynamic param);
void MergeOrAdd(dynamic param, Func<dynamic,dynamic, bool> checkOutFunc, Func<dynamic, dynamic, dynamic> mergeFunc);
}
}
54 changes: 54 additions & 0 deletions HistoricalReactiveCommand/ReactiveCommandWithHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,50 @@ public override IObservable<TResult> Execute()
return Execute(default!);
}

/// <summary>
/// Добавление в историю команд
/// </summary>
/// <param name="parameter">Параметры</param>
public void AddHistoryRecord(TParam parameter = default)
{
var historyEntry = new HistoryEntry(parameter, default(TResult), _commandKey);
var withHistory = this as IReactiveCommandWithHistory;
History.Record(historyEntry);
}

/// <summary>
/// Добавление в историю команд с обновление параметров последней команды
/// </summary>
/// <param name="parameter">Параметры</param>
private void Merge(TParam parameter = default)
{
var historyEntry = new HistoryEntry(parameter, default(TResult), _commandKey);
History.UndoPop();
History.Record(historyEntry);
}

/// <summary>
/// Добавление или обновление команды
/// </summary>
/// <param name="param">Параметры</param>
/// <param name="checkOutFunc">Функция проверки возможности слияния команд</param>
/// <param name="mergeFunc">Функция слияния команд</param>
public void MergeOrAdd(TParam param, Func<dynamic, dynamic, bool> checkOutFunc, Func<dynamic, dynamic, dynamic> mergeFunc)
{
var prevCommand = History.LastCommand();
if (prevCommand.CommandKey == _commandKey
&& prevCommand.CreationtTime == History.LastCommandTime
&& History.LastCommandTime.AddSeconds(10) > DateTime.UtcNow
&& checkOutFunc(param!, prevCommand.Parameter))
{
Merge(mergeFunc(param!, prevCommand.Parameter));
}
else
{
AddHistoryRecord(param);
}
}

protected override void Dispose(bool disposing)
{
_canExecuteSubscription.Dispose();
Expand All @@ -845,5 +889,15 @@ IObservable<HistoryEntry> IReactiveCommandWithHistory.Discard(HistoryEntry entry

return _discard.Execute(entry).Select(result => { entry.Result = result; return entry; });
}

void IReactiveCommandWithHistory.AddHistoryRecord(dynamic parameter)
{
AddHistoryRecord((TParam)parameter);
}

void IReactiveCommandWithHistory.MergeOrAdd(dynamic param, Func<dynamic,dynamic, bool> checkOutFunc, Func<dynamic, dynamic, dynamic> mergeFunc)
{
MergeOrAdd((TParam)param, checkOutFunc,mergeFunc);
}
}
}