Packages

Interactive flow graph component library for Phoenix LiveView

Current section

Files

Jump to
ex_flow_graph lib ex_flow history_manager.ex
Raw

lib/ex_flow/history_manager.ex

defmodule ExFlow.HistoryManager do
@moduledoc """
Manages undo/redo history using a command stack.
Maintains two stacks:
- past: commands that have been executed
- future: commands that have been undone
When a new command is executed, the future stack is cleared.
"""
alias ExFlow.Command
defstruct past: [], future: [], max_size: 50
@type t :: %__MODULE__{
past: [Command.t()],
future: [Command.t()],
max_size: pos_integer()
}
@doc """
Creates a new history manager with optional max size.
"""
def new(max_size \\ 50) do
%__MODULE__{max_size: max_size}
end
@doc """
Executes a command and adds it to history.
Clears the future stack.
"""
def execute(history, command, graph) do
case Command.execute(command, graph) do
{:ok, new_graph} ->
new_past = [command | history.past] |> Enum.take(history.max_size)
new_history = %{history | past: new_past, future: []}
{:ok, new_history, new_graph}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Undoes the last command if available.
"""
def undo(history, graph) do
case history.past do
[] ->
{:error, :nothing_to_undo}
[command | rest_past] ->
case Command.undo(command, graph) do
{:ok, new_graph} ->
new_history = %{
history
| past: rest_past,
future: [command | history.future]
}
{:ok, new_history, new_graph}
{:error, reason} ->
{:error, reason}
end
end
end
@doc """
Redoes the last undone command if available.
"""
def redo(history, graph) do
case history.future do
[] ->
{:error, :nothing_to_redo}
[command | rest_future] ->
case Command.execute(command, graph) do
{:ok, new_graph} ->
new_history = %{
history
| past: [command | history.past],
future: rest_future
}
{:ok, new_history, new_graph}
{:error, reason} ->
{:error, reason}
end
end
end
@doc """
Returns true if there are commands to undo.
"""
def can_undo?(history) do
history.past != []
end
@doc """
Returns true if there are commands to redo.
"""
def can_redo?(history) do
history.future != []
end
@doc """
Returns the description of the next command to undo, if any.
"""
def next_undo_description(history) do
case history.past do
[] -> nil
[command | _] -> Command.description(command)
end
end
@doc """
Returns the description of the next command to redo, if any.
"""
def next_redo_description(history) do
case history.future do
[] -> nil
[command | _] -> Command.description(command)
end
end
@doc """
Clears all history.
"""
def clear(history) do
%{history | past: [], future: []}
end
@doc """
Returns the number of commands in the past stack.
"""
def past_count(history) do
length(history.past)
end
@doc """
Returns the number of commands in the future stack.
"""
def future_count(history) do
length(history.future)
end
end