From 60f675a70c7d8d92789f5c634d9c557dfa03c3bf Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Wed, 7 Sep 2022 11:12:54 +0300 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B2=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=B1=D0=B5=D0=B7=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HistoricalReactiveCommand.csproj | 1 + HistoricalReactiveCommand/History.cs | 8 ++++++++ HistoricalReactiveCommand/IHistory.cs | 1 + .../IReactiveCommandWithHistory.cs | 1 + .../ReactiveCommandWithHistory.cs | 16 ++++++++++++++++ 5 files changed, 27 insertions(+) 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..39fb570 100644 --- a/HistoricalReactiveCommand/History.cs +++ b/HistoricalReactiveCommand/History.cs @@ -76,6 +76,14 @@ public IObservable Clear() return Observables.Unit; } + public void Add(HistoryEntry entry) + { + StackRedo.Clear(); + UpdateSubjects(true); + StackUndo.Push(entry); + UpdateSubjects(); + } + public void Dispose() { StackRedo.Clear(); diff --git a/HistoricalReactiveCommand/IHistory.cs b/HistoricalReactiveCommand/IHistory.cs index 9807736..dcae370 100644 --- a/HistoricalReactiveCommand/IHistory.cs +++ b/HistoricalReactiveCommand/IHistory.cs @@ -14,6 +14,7 @@ public interface IHistory IObservable Undo(Func> discard); IObservable Redo(Func> execute); IObservable Record(HistoryEntry entry, Func> execute); + void Add(HistoryEntry entry); IObservable Clear(); } } diff --git a/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs b/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs index bbfb285..6506686 100644 --- a/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs +++ b/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs @@ -7,5 +7,6 @@ public interface IReactiveCommandWithHistory : IReactiveCommand { IObservable Execute(HistoryEntry entry); IObservable Discard(HistoryEntry entry); + void AddHistoryRecord(dynamic param); } } \ No newline at end of file diff --git a/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs b/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs index 4646efa..f4059e0 100644 --- a/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs +++ b/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs @@ -823,6 +823,17 @@ 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); + } + protected override void Dispose(bool disposing) { _canExecuteSubscription.Dispose(); @@ -845,5 +856,10 @@ IObservable IReactiveCommandWithHistory.Discard(HistoryEntry entry return _discard.Execute(entry).Select(result => { entry.Result = result; return entry; }); } + + void IReactiveCommandWithHistory.AddHistoryRecord(dynamic parameter) + { + AddHistoryRecord((TParam)parameter); + } } } From c7463e5f9b25f1e389c405e86c2cb042a3a82d92 Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Wed, 7 Sep 2022 11:13:45 +0300 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=D0=97=D0=B0=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D1=82=20=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4?= =?UTF-8?q?=20=D0=B2=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8=D1=8E=20=D0=B2?= =?UTF-8?q?=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20Undo=20=D0=B8=20Redo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HistoricalReactiveCommand/History.cs | 6 ++++++ HistoricalReactiveCommand/HistoryContext.cs | 20 ++++++++++++++++++-- HistoricalReactiveCommand/IHistory.cs | 4 ++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/HistoricalReactiveCommand/History.cs b/HistoricalReactiveCommand/History.cs index 39fb570..4085fb8 100644 --- a/HistoricalReactiveCommand/History.cs +++ b/HistoricalReactiveCommand/History.cs @@ -17,6 +17,10 @@ public class History : IHistory, IDisposable private readonly Subject _canUndo = new Subject(); private readonly Subject _canRedo = new Subject(); private readonly Subject _canClear = new Subject(); + /// + /// Можно ли добавлять вложенные команды в стек + /// + public bool CanAddInternalCommand { get; set; } = true; public History(string id) { @@ -40,6 +44,7 @@ public IObservable Undo(Func Redo(Func 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); @@ -109,5 +117,13 @@ 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); + } + } } } diff --git a/HistoricalReactiveCommand/IHistory.cs b/HistoricalReactiveCommand/IHistory.cs index dcae370..c4903db 100644 --- a/HistoricalReactiveCommand/IHistory.cs +++ b/HistoricalReactiveCommand/IHistory.cs @@ -10,6 +10,10 @@ public interface IHistory IObservable CanRedo { get; } IObservable CanRecord { get; } IObservable CanClear { get; } + /// + /// Можно ли вызывать команды внутри команд + /// + bool CanAddInternalCommand { get; set; } IObservable Undo(Func> discard); IObservable Redo(Func> execute); From 07439db91bb2f61a4389a885942f835f00f6f6e7 Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Wed, 7 Sep 2022 11:23:33 +0300 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2=20His?= =?UTF-8?q?toryEntry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HistoricalReactiveCommand/HistoryEntry.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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; } } } From 3dea23af6a08fea4eb90403d7087c037f77b393a Mon Sep 17 00:00:00 2001 From: Kibnet Philosoff Date: Wed, 7 Sep 2022 11:24:32 +0300 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20MergeOrAd?= =?UTF-8?q?d=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BB=D0=B8=D1=8F=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BE=D0=B4=D0=B8=D0=BD=D0=B0=D0=BA=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D0=B4=D0=BD=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HistoricalReactiveCommand/History.cs | 20 +++++++++- HistoricalReactiveCommand/HistoryContext.cs | 12 ++++++ HistoricalReactiveCommand/IHistory.cs | 14 +++++++ .../IReactiveCommandWithHistory.cs | 1 + .../ReactiveCommandWithHistory.cs | 38 +++++++++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/HistoricalReactiveCommand/History.cs b/HistoricalReactiveCommand/History.cs index 4085fb8..1ef3425 100644 --- a/HistoricalReactiveCommand/History.cs +++ b/HistoricalReactiveCommand/History.cs @@ -12,11 +12,22 @@ 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; } /// /// Можно ли добавлять вложенные команды в стек /// @@ -69,6 +80,7 @@ public IObservable Record(HistoryEntry entry, Func { StackUndo.Push(updatedEntry); + LastCommandTime = entry.CreationtTime; UpdateSubjects(); }); } @@ -87,9 +99,15 @@ 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 3b0ebc1..da657de 100644 --- a/HistoricalReactiveCommand/HistoryContext.cs +++ b/HistoricalReactiveCommand/HistoryContext.cs @@ -90,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; } @@ -125,5 +132,10 @@ internal void Record(HistoryEntry entry) _history.Add(entry); } } + + /// + /// Удление верхнего элемента + /// + public void UndoPop() => _history.UndoPop(); } } diff --git a/HistoricalReactiveCommand/IHistory.cs b/HistoricalReactiveCommand/IHistory.cs index c4903db..8e87df6 100644 --- a/HistoricalReactiveCommand/IHistory.cs +++ b/HistoricalReactiveCommand/IHistory.cs @@ -14,11 +14,25 @@ public interface IHistory /// Можно ли вызывать команды внутри команд /// 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 6506686..c982d8e 100644 --- a/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs +++ b/HistoricalReactiveCommand/IReactiveCommandWithHistory.cs @@ -8,5 +8,6 @@ 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 f4059e0..1a4d260 100644 --- a/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs +++ b/HistoricalReactiveCommand/ReactiveCommandWithHistory.cs @@ -834,6 +834,39 @@ public void AddHistoryRecord(TParam parameter = default) 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(); @@ -861,5 +894,10 @@ void IReactiveCommandWithHistory.AddHistoryRecord(dynamic parameter) { AddHistoryRecord((TParam)parameter); } + + void IReactiveCommandWithHistory.MergeOrAdd(dynamic param, Func checkOutFunc, Func mergeFunc) + { + MergeOrAdd((TParam)param, checkOutFunc,mergeFunc); + } } }