Skip to content

Commit

Permalink
Merge pull request #21 from le-nn/feature/history_api_improvements
Browse files Browse the repository at this point in the history
Feature/history api improvements
  • Loading branch information
le-nn authored Jul 21, 2023
2 parents f5cfff3 + 6ba83b2 commit 88dbbac
Show file tree
Hide file tree
Showing 54 changed files with 1,117 additions and 662 deletions.
1 change: 1 addition & 0 deletions Memento.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{95938898-9
docs\Blazor.md = docs\Blazor.md
docs\Middleware.md = docs\Middleware.md
docs\README.md = docs\README.md
docs\RedoUndo.md = docs\RedoUndo.md
docs\ReduxDevTools.md = docs\ReduxDevTools.md
EndProjectSection
EndProject
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ https://github.com/le-nn/memento-js
* Every Reducer that processes in the action will create new state to reflect the old state combined with the changes expected for the action.
* To change state our app should Dispatch via Reducer in the action method

## Overview
## Store Overview

This is an C# and Blazor example that implements counter.

Expand Down Expand Up @@ -143,7 +143,7 @@ public class AsyncCounterStore : FluxStore<AsyncCounterState, AsyncCounterComman

```

Razor view
Blazor view in Razor
```razor
@page "/counter"
@inherits ObserverComponet
Expand Down Expand Up @@ -176,4 +176,5 @@ Razor view
[See](./docs/README.md)

# License

Designed with ♥ by le-nn. Licensed under the MIT License.
16 changes: 5 additions & 11 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,13 @@ You can do state history and time travel.

## Tutorials



| Link | Summary |
| Link | Summary |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [BasicConcept with C#](./BasicConcept.md) | The tutorials for implemented with pure C# in simple console application. |
| <br>[Update UI with Blazor](./Blazor.md) | Practical Uses of the Framework. In practice, it is mostly used with UI frameworks.<br>Here is a tutorial on how to use it with Blazor. |
| [Middleware](./Middleware.md) | Middleware can be implemented to interrupt the process when updating the state.<br>Middleware can be extended for various purposes, such as implementing your own Logger or supporting ReduxDevTools. |
| [BasicConcept with C#](./BasicConcept.md) | The tutorials for implemented with pure C# in simple console application. |
| [Update UI with Blazor](./Blazor.md) | Practical Uses of the Framework. In practice, it is mostly used with UI frameworks.<br>Here is a tutorial on how to use it with Blazor. |
| [Middleware](./Middleware.md) | Middleware can be implemented to interrupt the process when updating the state.<br>Middleware can be extended for various purposes, such as implementing your own Logger or supporting ReduxDevTools. |
| [Redux Dev Tools](./ReduxDevTools.md) | ReduxDevTools is a tool for debugging application's state changes.<br><br>State can be time traveled and history can be viewed in ReduxDevTools.<br> |





| [Redo / Undo](./RedoUndo.md) | Redo/Undo is a feature that allows you to undo and redo state changes. |

## Samples

Expand Down
142 changes: 142 additions & 0 deletions docs/RedoUndo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# ReDo/UnDo

```Memento.Core.History.HistoryManager``` is a class that manages the redo/undo state context.
It gives you a generic ReDo/UnDo.

Here is an example of how to use it.

```cs

@using Memento.Core.History;

<PageTitle>Index</PageTitle>

<h1>Redo / Undo Counter</h1>

<button disabled="@(!_historyManager.CanUnDo)" @onclick="() => _historyManager.UnDoAsync()">UnDo</button>
<button disabled="@(!_historyManager.CanReDo)" @onclick="() => _historyManager.ReDoAsync()">ReDo</button>

<h2>@_count</h2>

<button @onclick="CountUp">Count Up</button>

@code {
readonly HistoryManager _historyManager = new() { MaxHistoryCount = 20 };

int _count = 0;

async Task CountUp() {
await _historyManager.CommitAsync(
async () => {
var count = _count;
_count++;
return new {
Count = count,
};
},
async state => {
_count = state.Count;
}
);
}
}

```


MaxHistoryCount is the maximum number that can be saved.
Disabling button with CanUnDo/CanReDo.

Take a snapshot of the current State with CommitAsync.

The first argument callback is called when "Do" or "ReDo" is performed, and retains the returned Context as a Snapshot.
The first callback should be implemented to create and take snapshot of the state when "Do" or "ReDo" performed.

The second argument callback is called when "UnDo" is performed.
The second callback should be implemented to Restore the state from the snapshot of the state when "UnDo" performed.

# With Store

```Memento.Core.MementoStore``` or ```Memento.Core.FluxMementoStore``` allows you to manage the redo/undo state context in the store.
Specify the HistoryManager instance as the second argument of base() constructor. You can share context across stores.
Invoking CommitAsync preserves the current State, which can be restored with UnDoAsync and ReDoAsync.

Unlike HistoryManager.CommitAsync, there is no need to manually assign state.
The state at the time Store.CommitAsync was invoked is automatically restored.

The first argument callback is called when "Do" or "ReDo" is performed,
The returned value is retained as a payload to receive when the second argument callback is called.
The first callback should be implemented to create a payload when "Do" or "ReDo" performed.

The second argument callback is called when "UnDo" is performed.
The second callback should be implemented handling such as removing items with a received payload from the DB or Server etc.

## Sample with ToDo

```cs

public record RedoUndoTodoState {
public ImmutableArray<Todo> Todos { get; init; } = ImmutableArray.Create<Todo>();

public bool IsLoading { get; init; }
}

public class RedoUndoTodoStore : MementoStore<RedoUndoTodoState> {
readonly ITodoService _todoService;

public RedoUndoTodoStore(ITodoService todoService) : base(() => new(), new() { MaxHistoryCount = 20 }) {
_todoService = todoService;
}

public async Task CreateNewAsync(string text) {
var id = Guid.NewGuid();
await CommitAsync(
async () => {
var item = await _todoService.CreateItemAsync(id, text);
Mutate(state => state with {
Todos = state.Todos.Add(item),
});

return item;
},
async todo => {
await _todoService.RemoveAsync(todo.Payload.TodoId);
}
);
}

public async Task LoadAsync() {
Mutate(state => state with { IsLoading = true });
var items = await _todoService.FetchItemsAsync();
Mutate(state => state with { Todos = items, });
Mutate(state => state with { IsLoading = false });
}

public async Task ToggleIsCompletedAsync(Guid id) {
await CommitAsync(
async () => {
var state = State;
var item = await _todoService.ToggleCompleteAsync(id)
?? throw new Exception("Failed to toggle an item in Do or ReDo.");
Mutate(state => state with {
Todos = state.Todos.Replace(
state.Todos.Where(x => id == x.TodoId).First(),
item
)
});

return item;
},
async p => {
var item = await _todoService.ToggleCompleteAsync(id)
?? throw new Exception("Failed to toggle an item in UnDo.");
}
);
}
}

```

DEMO

https://le-nn.github.io/memento/todo
8 changes: 8 additions & 0 deletions samples/Memento.Sample.Blazor/Components/CountDisplay.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@using Memento.Sample.Blazor.Stores;
@inherits ObserverComponent
@inject AsyncCounterStore AsyncCounterStore

<div class="p-4 bg-opacity-10 bg-dark rounded-2">
<h1 class="">Observed Store Component</h1>
<div>Observed Count: @AsyncCounterStore.State.Count</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
@using Memento.Sample.Blazor.Stores
@using Memento.Sample.Blazor.Stores
@using System.Text.Json

@page "/counter"
@inherits ObserverComponent
@inject AsyncCounterStore AsyncCounterStore

<PageTitle>Counter</PageTitle>

<div>
<h1 class="mt-5">Async Counter</h1>
<div class="p-4 mt-4 bg-opacity-10 bg-dark rounded-2">
<h1 class="">Async Counter Component</h1>
<h2>Current count: @AsyncCounterStore.State.Count</h2>
<p>Loading: @AsyncCounterStore.State.IsLoading</p>

<div>
<button class="mt-3 btn btn-primary" @onclick="IncrementCount">Count up</button>
<button class="mt-3 btn btn-primary" @onclick="CountupMany">Count up 100 times</button>
<button class="mt-3 btn btn-primary" @onclick="CountUpMany">Count up 100 times</button>
</div>

<div class="mt-5">
Expand All @@ -32,7 +29,7 @@
<h3>Count up with Amount</h3>
<input @bind-value="_amount" />
</div>
<button class="mt-3 btn btn-primary" @onclick="CountupWithAmount">Count up with amount</button>
<button class="mt-3 btn btn-primary" @onclick="CountUpWithAmount">Count up with amount</button>

<div class="mt-5">
<h3>Set count</h3>
Expand All @@ -53,11 +50,11 @@
await AsyncCounterStore.CountUpAsync();
}

void CountupMany() {
void CountUpMany() {
AsyncCounterStore.CountUpManyTimes(100);
}

void CountupWithAmount() {
void CountUpWithAmount() {
AsyncCounterStore.CountUpWithAmount(_amount);
}

Expand Down
10 changes: 10 additions & 0 deletions samples/Memento.Sample.Blazor/Pages/CounterPage.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@using Memento.Sample.Blazor.Components
@using Memento.Sample.Blazor.Stores
@using System.Text.Json

@page "/counter"

<PageTitle>Counter</PageTitle>

<CountDisplay />
<Counter />
8 changes: 4 additions & 4 deletions samples/Memento.Sample.Blazor/Pages/FluxCounter.razor
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<div>
<button class="mt-3 btn btn-primary" @onclick="IncrementCount">Count up</button>
<button class="mt-3 btn btn-primary" @onclick="CountupMany">Count up 100 times</button>
<button class="mt-3 btn btn-primary" @onclick="CountUpMany">Count up 100 times</button>
</div>

<div class="mt-5">
Expand All @@ -32,7 +32,7 @@
<h3>Count up with Amount</h3>
<input @bind-value="_amount" />
</div>
<button class="mt-3 btn btn-primary" @onclick="CountupWithAmount">Count up with amount</button>
<button class="mt-3 btn btn-primary" @onclick="CountUpWithAmount">Count up with amount</button>

<div class="mt-5">
<h3>Set count</h3>
Expand All @@ -53,11 +53,11 @@
await AsyncCounterStore.CountUpAsync();
}

void CountupMany() {
void CountUpMany() {
AsyncCounterStore.CountUpManyTimes(100);
}

void CountupWithAmount() {
void CountUpWithAmount() {
AsyncCounterStore.CountUpWithAmount(_amount);
}

Expand Down
6 changes: 3 additions & 3 deletions samples/Memento.Sample.Blazor/Pages/FluxRedoUndoTodo.razor
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

<div class="d-flex shadow p-1 rounded-pill bg-white">
<input @onkeydown="HandleKeyEnter"
@bind="text"
@bind="text"
placeholder="Input todo text"
class="form-control form-control-sm input bg-transparent" />
<button class="btn btn-primary rounded-circle"
Expand Down Expand Up @@ -82,11 +82,11 @@
}

async Task Redo() {
await this.RedoUndoTodoStore.ReExecuteAsync();
await this.RedoUndoTodoStore.ReDoAsync();
}

async Task Undo() {
await this.RedoUndoTodoStore.UnExecuteAsync();
await this.RedoUndoTodoStore.UnDoAsync();
}

async Task HandleToggleTodo(Guid id) {
Expand Down
4 changes: 2 additions & 2 deletions samples/Memento.Sample.Blazor/Pages/RedoUndoTodo.razor
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@
}

async Task Redo() {
await this.RedoUndoTodoStore.ReExecuteAsync();
await this.RedoUndoTodoStore.ReDoAsync();
}

async Task Undo() {
await this.RedoUndoTodoStore.UnExecuteAsync();
await this.RedoUndoTodoStore.UnDoAsync();
}

async Task HandleToggleTodo(Guid id) {
Expand Down
Loading

0 comments on commit 88dbbac

Please sign in to comment.