Fluxor.Undo
1.0.1
dotnet add package Fluxor.Undo --version 1.0.1
NuGet\Install-Package Fluxor.Undo -Version 1.0.1
<PackageReference Include="Fluxor.Undo" Version="1.0.1" />
paket add Fluxor.Undo --version 1.0.1
#r "nuget: Fluxor.Undo, 1.0.1"
// Install Fluxor.Undo as a Cake Addin #addin nuget:?package=Fluxor.Undo&version=1.0.1 // Install Fluxor.Undo as a Cake Tool #tool nuget:?package=Fluxor.Undo&version=1.0.1
Fluxor.Undo
Fluxor.Undo is a library to add redo/undo functionality to Fluxor.
Goal
The aim of Fluxor.Undo is removing the hassle of implementing your own undo/redo functionality. The idea is inspired by redux-undo although the implementation is completely different.
Installation
You can download the latest release / pre-release NuGet package from nuget:
Package: | ||
---|---|---|
Fluxor.Undo |
Setup undoable state
Steps to change your regular state to an undoable state:
1) Change your Feature to an Undoable feature
Change your state with FeatureStateAtrribute
[FeatureState(Name = "Counter", CreateInitialStateMethodName = nameof(CreateInitialState))]
public sealed record CounterState(int ClickCount)
{
public static CounterState CreateInitialState()
=> new(0);
}
to
public sealed record CounterState(int ClickCount);
[FeatureState(Name = "Counter", CreateInitialStateMethodName = nameof(CreateInitialState))]
public sealed record UndoableCounterState : Undoable<UndoableCounterState, CounterState>
{
public static UndoableCounterState CreateInitialState()
=> new() { Present = new(0) };
}
// Or when net6:
public sealed record CounterState(int ClickCount);
[FeatureState(Name = "Counter", CreateInitialStateMethodName = nameof(CreateInitialState))]
public sealed record UndoableCounterState(CounterState Present) : Undoable<UndoableCounterState, CounterState>(Present)
{
public static UndoableCounterState CreateInitialState()
=> new(new CounterState(0));
};
When using Feature baseclass change
public sealed record CounterState(int ClickCount);
public sealed class CounterFeature : Feature<CounterState>
{
public override string GetName()
=> "Counter";
protected override CounterState GetInitialState()
=> new(0);
}
to
public sealed record CounterState(int ClickCount);
public sealed record UndoableCounterState : Undoable<UndoableCounterState, CounterState>;
public sealed class UndoableCounterFeature : Feature<UndoableCounterState>
{
public override string GetName()
=> "Counter";
protected override UndoableCounterState GetInitialState()
=> new() { Present = new(0) };
}
// Or when net6:
public sealed record CounterState(int ClickCount);
public sealed record UndoableCounterState(CounterState Present) : Undoable<UndoableCounterState, CounterState>(Present);
public sealed class UndoableCounterFeature : Feature<UndoableCounterState>
{
public override string GetName()
=> "Counter";
protected override UndoableCounterState GetInitialState()
=> new(new CounterState(0));
}
2) Update your reducer Change your reducer from
public static class Reducers
{
[ReducerMethod]
public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction action)
=> state with
{
ClickCount = state.ClickCount + action.Amount,
};
}
to
public class Reducers : UndoableReducers<UndoableCounterState>
{
[ReducerMethod]
public static UndoableCounterState ReduceIncrementCounterAction(UndoableCounterState state, IncrementCounterAction action)
=> state.WithNewPresent(p => p with
{
ClickCount = p.ClickCount + action.Amount,
});
}
3) Update your injected IState properties Change setting of properties in your Razor pages from
[Inject]
private IState<CounterState> CounterState { get; set; } = null!;
to
[Inject]
private IState<UndoableCounterState> UndoableCounterState { get; set; } = null!;
4) Update usages of your state Change usage in your Razor pages from
<p>Current count: @CounterState.Value.ClickCount</p>
to
<p>Current count: @UndoableCounterState.Value.Present.ClickCount</p>
5) Add some navigation buttons
<button class="btn btn-secondary" @onclick=@(() => Dispatcher.Dispatch(new UndoAllAction<UndoableCounterState>())) disabled="@UndoableCounterState.Value.HasNoPast"><<</button>
<button class="btn btn-secondary" @onclick=@(() => Dispatcher.Dispatch(new UndoAction<UndoableCounterState>())) disabled="@UndoableCounterState.Value.HasNoPast"><</button>
<button class="btn btn-secondary" @onclick=@(() => Dispatcher.Dispatch(new RedoAction<UndoableCounterState>())) disabled="@UndoableCounterState.Value.HasNoFuture">></button>
<button class="btn btn-secondary" @onclick=@(() => Dispatcher.Dispatch(new RedoAllAction<UndoableCounterState>())) disabled="@UndoableCounterState.Value.HasNoFuture">>></button>
Also see example project in solution. Here both the Fluxor counter as Fluxor.Undo counter are implemented.
Available undo/redo actions
Dispatcher.Dispatch(new UndoAction<T>()); // undo the last action
Dispatcher.Dispatch(new UndoAllAction<T>()); // undo all actions
Dispatcher.Dispatch(new RedoAction<T>()); // redo the last action
Dispatcher.Dispatch(new RedoAllAction<T>()); // redo all actions
Dispatcher.Dispatch(new JumpAction<T>(-2)); // undo 2 steps
Dispatcher.Dispatch(new JumpAction<T>(5)); // redo 5 steps
Helper methods on state
public sealed record CounterState(int ClickCount);
public sealed record UndoableCounterState : Undoable<UndoableCounterState, CounterState>;
var state = new UndoableCounterState { Present = new CounterState { ClickCount = 0}};
var newState1 = state.WithNewPresent(p => p with { ClickCount = p.ClickCount + 1 }); // Moves current present to past and sets new present
var newState2 = state.WithNewPresent(new CounterState { ClickCount = 1}); // Moves current present to past and sets new present
var newState3 = state.WithInlineEditedPresent(p => p with { ClickCount = p.ClickCount + 1 }); // Does NOT move current present to past; it will replace current present
var newState4 = state.WithInlineEditedPresent(new CounterState { ClickCount = 1}); // Does NOT move current present to past; it will replace current present
Tips
- When you are allowing undo/redo, the undo/redo is done on client side. So make sure that user knows that undo-ing does not alter data on server. There is a basic implementation in the example project in solution; page: Fluxor.Undo (Persist). Can be used as inspiration!
- If you are using net6; upgrade to net7 so you can use the parameterless ctors and use the required properties 😃.
Release notes
See the Releases page.
Versioning
Fluxor.Undo follows Semantic Versioning 2.0.0 for the releases published to nuget.org.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.0.1 | 445 | 12/30/2022 |
1.0.1-prerelease-2022122820... | 152 | 12/28/2022 |
1.0.0 | 526 | 12/28/2022 |
1.0.0-prerelease-2022122815... | 152 | 12/28/2022 |