diff --git a/HistoricalReactiveCommand/HistoricalReactiveCommand.csproj b/HistoricalReactiveCommand/HistoricalReactiveCommand.csproj index f19debc..ab734fe 100644 --- a/HistoricalReactiveCommand/HistoricalReactiveCommand.csproj +++ b/HistoricalReactiveCommand/HistoricalReactiveCommand.csproj @@ -15,6 +15,7 @@ + diff --git a/HistoricalReactiveCommand/History.cs b/HistoricalReactiveCommand/History.cs index 54991b0..1ef3425 100644 --- a/HistoricalReactiveCommand/History.cs +++ b/HistoricalReactiveCommand/History.cs @@ -12,12 +12,27 @@ public class History : IHistory, IDisposable { private Stack StackRedo { get; } = new Stack(); private Stack StackUndo { get; } = new Stack(); - + + public HistoryEntry LastCommand() + { + return StackUndo.Peek(); + } + private readonly Subject _canRecord = new Subject(); private readonly Subject _canUndo = new Subject(); private readonly Subject _canRedo = new Subject(); private readonly Subject _canClear = new Subject(); + /// + /// Время последней добавленной команды + /// + /// Время именно добавленной команды, а не из стека + public DateTime LastCommandTime { get; set; } + /// + /// Можно ли добавлять вложенные команды в стек + /// + public bool CanAddInternalCommand { get; set; } = true; + public History(string id) { Id = id; @@ -40,6 +55,7 @@ public IObservable Undo(Func Redo(Func Record(HistoryEntry entry, Func { StackUndo.Push(updatedEntry); + LastCommandTime = entry.CreationtTime; UpdateSubjects(); }); } @@ -76,6 +94,20 @@ public IObservable 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(); diff --git a/HistoricalReactiveCommand/HistoryContext.cs b/HistoricalReactiveCommand/HistoryContext.cs index 1e78c26..da657de 100644 --- a/HistoricalReactiveCommand/HistoryContext.cs +++ b/HistoricalReactiveCommand/HistoryContext.cs @@ -62,7 +62,11 @@ internal HistoryContext(IHistory history, IScheduler outputScheduler) .CombineLatest(history.CanUndo, (recordable, executable) => recordable && executable); Undo = ReactiveCommand.CreateFromObservable( - unit => history.Undo(entry => ResolveCommand(entry).Discard(entry)), + unit => history.Undo(entry => + { + history.CanAddInternalCommand = false; + return ResolveCommand(entry).Discard(entry); + }), CanUndo, outputScheduler); @@ -70,7 +74,11 @@ internal HistoryContext(IHistory history, IScheduler outputScheduler) .CombineLatest(history.CanRedo, (recordable, executable) => recordable && executable); Redo = ReactiveCommand.CreateFromObservable( - unit => history.Redo(entry => ResolveCommand(entry).Execute(entry)), + unit => history.Redo(entry => + { + history.CanAddInternalCommand = false; + return ResolveCommand(entry).Execute(entry); + }), CanRedo, outputScheduler); @@ -82,6 +90,13 @@ internal HistoryContext(IHistory history, IScheduler outputScheduler) public IObservable CanRecord => _history.CanRecord; + public HistoryEntry LastCommand() + { + return _history.LastCommand(); + } + + public DateTime LastCommandTime => _history.LastCommandTime; + public ReactiveCommand Undo { get; } public ReactiveCommand Redo { get; } @@ -109,5 +124,18 @@ private static IReactiveCommandWithHistory ResolveCommand(HistoryEntry entry) } internal IObservable Record(HistoryEntry entry, Func> execute) => _history.Record(entry, execute); + + internal void Record(HistoryEntry entry) + { + if (_history.CanAddInternalCommand) + { + _history.Add(entry); + } + } + + /// + /// Удление верхнего элемента + /// + public void UndoPop() => _history.UndoPop(); } } diff --git a/HistoricalReactiveCommand/HistoryEntry.cs b/HistoricalReactiveCommand/HistoryEntry.cs index 744fd5c..6cd58a8 100644 --- a/HistoricalReactiveCommand/HistoryEntry.cs +++ b/HistoricalReactiveCommand/HistoryEntry.cs @@ -1,4 +1,7 @@ -namespace HistoricalReactiveCommand +using System; +using System.Data; + +namespace HistoricalReactiveCommand { public class HistoryEntry { @@ -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; } } } diff --git a/HistoricalReactiveCommand/IHistory.cs b/HistoricalReactiveCommand/IHistory.cs index 9807736..8e87df6 100644 --- a/HistoricalReactiveCommand/IHistory.cs +++ b/HistoricalReactiveCommand/IHistory.cs @@ -10,10 +10,29 @@ public interface IHistory IObservable CanRedo { get; } IObservable CanRecord { get; } IObservable CanClear { get; } + /// + /// Можно ли вызывать команды внутри команд + /// + bool CanAddInternalCommand { get; set; } + /// + /// Время последней добавленной команды + /// + /// Время именно добавленной команды, а не из стека + DateTime LastCommandTime { get; set; } + + /// + /// Последняя команда + /// + HistoryEntry LastCommand(); IObservable Undo(Func> discard); IObservable Redo(Func> execute); IObservable Record(HistoryEntry entry, Func> execute); + void Add(HistoryEntry entry); + /// + /// Удаление верхнего элемента + /// + void UndoPop(); IObservable Clear(); } } diff --git a/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs b/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs index bbfb285..c982d8e 100644 --- a/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs +++ b/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs @@ -7,5 +7,7 @@ public interface IReactiveCommandWithHistory : IReactiveCommand { IObservable Execute(HistoryEntry entry); IObservable Discard(HistoryEntry entry); + void AddHistoryRecord(dynamic param); + void MergeOrAdd(dynamic param, Func checkOutFunc, Func mergeFunc); } } \ No newline at end of file diff --git a/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs b/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs index 4646efa..1a4d260 100644 --- a/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs +++ b/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs @@ -823,6 +823,50 @@ public override IObservable Execute() return Execute(default!); } + /// + /// Добавление в историю команд + /// + /// Параметры + public void AddHistoryRecord(TParam parameter = default) + { + var historyEntry = new HistoryEntry(parameter, default(TResult), _commandKey); + var withHistory = this as IReactiveCommandWithHistory; + History.Record(historyEntry); + } + + /// + /// Добавление в историю команд с обновление параметров последней команды + /// + /// Параметры + private void Merge(TParam parameter = default) + { + var historyEntry = new HistoryEntry(parameter, default(TResult), _commandKey); + History.UndoPop(); + History.Record(historyEntry); + } + + /// + /// Добавление или обновление команды + /// + /// Параметры + /// Функция проверки возможности слияния команд + /// Функция слияния команд + public void MergeOrAdd(TParam param, Func checkOutFunc, Func 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(); @@ -845,5 +889,15 @@ IObservable 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 checkOutFunc, Func mergeFunc) + { + MergeOrAdd((TParam)param, checkOutFunc,mergeFunc); + } } }