Skip to content

Commit

Permalink
Fix #16, Fix #20
Browse files Browse the repository at this point in the history
  • Loading branch information
experiarms1 committed Jul 21, 2023
1 parent 9a11301 commit 6ba83b2
Show file tree
Hide file tree
Showing 48 changed files with 1,028 additions and 625 deletions.
108 changes: 86 additions & 22 deletions docs/RedoUndo.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,79 @@
# ReDo/UnDo


```Memento.Core.History.HistoryManager``` is a class that manages the redo / undo of the state.
```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.

```cs
Take a snapshot of the current State with CommitAsync.

using Memento.Sample.Blazor.Todos;
using System.Collections.Immutable;
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.

namespace Memento.Sample.Blazor.Stores;
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>();
Expand All @@ -26,53 +82,61 @@ public record RedoUndoTodoState {
}

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

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

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

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

public async Task LoadAsync() {
Mutate(state => state with { IsLoading = true });
var items = await TodoService.FetchItemsAsync();
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 item = await TodoService.ToggleCompleteAsync(id)
?? throw new Exception();
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 () => {
var item = await TodoService.ToggleCompleteAsync(id)
?? throw new Exception();
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
34 changes: 15 additions & 19 deletions samples/Memento.Sample.Blazor/Stores/FluxRedoUndoTodoStore.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Memento.Core.History;
using Memento.Sample.Blazor.Todos;
using System.Collections.Immutable;

Expand All @@ -20,15 +21,10 @@ public record EndLoading : FluxRedoUndoTodoCommand;
}

public class FluxRedoUndoTodoStore : FluxMementoStore<FluxRedoUndoTodoState, FluxRedoUndoTodoCommand> {
ITodoService TodoService { get; }
readonly ITodoService _todoService;

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

static FluxRedoUndoTodoState Reducer(FluxRedoUndoTodoState state, FluxRedoUndoTodoCommand command) {
Expand All @@ -53,35 +49,35 @@ static FluxRedoUndoTodoState Reducer(FluxRedoUndoTodoState state, FluxRedoUndoTo

public async Task CreateNewAsync(string text) {
await CommitAsync(
() => {
return ValueTask.FromResult(Guid.NewGuid());
},
async id => {
var item = await TodoService.CreateItemAsync(id, text);
async () => {
var id = Guid.NewGuid();
var item = await _todoService.CreateItemAsync(id, text);
Dispatch(new Append(item));
return item;
},
async id => {
await TodoService.RemoveAsync(id);
async todo => {
await _todoService.RemoveAsync(todo.Payload.TodoId);
}
);
}

public async Task LoadAsync() {
Dispatch(new BeginLoading());
var items = await TodoService.FetchItemsAsync();
var items = await _todoService.FetchItemsAsync();
Dispatch(new SetItems(items));
Dispatch(new EndLoading());
}

public async Task ToggleIsCompletedAsync(Guid id) {
await CommitAsync(
async () => {
var item = await TodoService.ToggleCompleteAsync(id)
var item = await _todoService.ToggleCompleteAsync(id)
?? throw new Exception();
Dispatch(new Replace(id, item));
return item;
},
async () => {
var item = await TodoService.ToggleCompleteAsync(id)
async todo => {
var item = await _todoService.ToggleCompleteAsync(todo.Payload.TodoId)
?? throw new Exception();
}
);
Expand Down
Loading

0 comments on commit 6ba83b2

Please sign in to comment.