Another implementation of undo/redo
This commit is contained in:
@@ -1,30 +1,24 @@
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
|
||||
from fasthtml.components import *
|
||||
|
||||
from components.BaseComponent import BaseComponentSingleton
|
||||
from components.undo_redo.assets.icons import icon_redo, icon_undo
|
||||
from components.undo_redo.commands import UndoRedoCommandManager
|
||||
from components.undo_redo.constants import UNDO_REDO_INSTANCE_ID
|
||||
from components.undo_redo.constants import UNDO_REDO_INSTANCE_ID, UndoRedoAttrs
|
||||
from components_helpers import mk_icon, mk_tooltip
|
||||
|
||||
logger = logging.getLogger("UndoRedoApp")
|
||||
|
||||
|
||||
class CommandHistory(ABC):
|
||||
def __init__(self, name, desc, owner):
|
||||
self.name = name
|
||||
self.desc = desc
|
||||
self.owner = owner
|
||||
|
||||
@abstractmethod
|
||||
def undo(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def redo(self):
|
||||
pass
|
||||
@dataclass
|
||||
class CommandHistory:
|
||||
attrs: UndoRedoAttrs
|
||||
digest: str | None # digest to remember
|
||||
entry: str # digest to remember
|
||||
key: str # key
|
||||
path: str # path within the key if only on subitem needs to be updated
|
||||
|
||||
|
||||
class UndoRedo(BaseComponentSingleton):
|
||||
@@ -35,35 +29,73 @@ class UndoRedo(BaseComponentSingleton):
|
||||
self.index = -1
|
||||
self.history = []
|
||||
self._commands = UndoRedoCommandManager(self)
|
||||
self._db_engine = settings_manager.get_db_engine()
|
||||
|
||||
def push(self, command: CommandHistory):
|
||||
self.history = self.history[:self.index + 1]
|
||||
def snapshot(self, undo_redo_attrs: UndoRedoAttrs, entry, key, path=None):
|
||||
digest = self._settings_manager.get_digest(self._session, entry) # get the current digest (the last one)
|
||||
|
||||
# init the history if this is the first call
|
||||
if len(self.history) == 0:
|
||||
digest_history = self._settings_manager.history(self._session, entry, digest, 2)
|
||||
command = CommandHistory(undo_redo_attrs,
|
||||
digest_history[1] if len(digest_history) > 1 else None,
|
||||
entry,
|
||||
key,
|
||||
path)
|
||||
self.history.append(command)
|
||||
self.index = 0
|
||||
|
||||
command = CommandHistory(undo_redo_attrs, digest, entry, key, path)
|
||||
|
||||
self.history = self.history[:self.index + 1] #
|
||||
self.history.append(command)
|
||||
self.index += 1
|
||||
self.index = len(self.history) - 1
|
||||
|
||||
def undo(self):
|
||||
logger.debug(f"Undo command")
|
||||
if self.index < 0 :
|
||||
if self.index < 1:
|
||||
logger.debug(f" No command to undo.")
|
||||
return self
|
||||
|
||||
command = self.history[self.index]
|
||||
logger.debug(f" Undoing command {command.name} ({command.desc})")
|
||||
res = command.undo()
|
||||
current = self.history[self.index]
|
||||
current_state = self._settings_manager.load(self._session, None, digest=current.digest)
|
||||
|
||||
previous = self.history[self.index - 1]
|
||||
previous_state = self._settings_manager.load(self._session, None, digest=previous.digest)
|
||||
|
||||
# reapply the state
|
||||
current_state[current.key] = previous_state[current.key]
|
||||
self._settings_manager.save(self._session, current.entry, current_state)
|
||||
|
||||
self.index -= 1
|
||||
return self, res
|
||||
|
||||
if current.attrs.on_undo is not None:
|
||||
return self, current.attrs.on_undo()
|
||||
else:
|
||||
return self
|
||||
|
||||
def redo(self):
|
||||
logger.debug("Redo command")
|
||||
if self.index == len(self.history) - 1:
|
||||
logger.debug(f" No command to redo.")
|
||||
logger.debug(f"Redo command")
|
||||
if self.index >= len(self.history) - 1:
|
||||
logger.debug(f" No command to undo.")
|
||||
return self
|
||||
|
||||
current = self.history[self.index]
|
||||
current_state = self._settings_manager.load(self._session, None, digest=current.digest)
|
||||
|
||||
next_ = self.history[self.index + 1]
|
||||
next_state = self._settings_manager.load(self._session, None, digest=next_.digest)
|
||||
|
||||
# reapply the state
|
||||
current_state[current.key] = next_state[current.key]
|
||||
self._settings_manager.save(self._session, current.entry, current_state)
|
||||
|
||||
self.index += 1
|
||||
command = self.history[self.index]
|
||||
logger.debug(f" Redoing command {command.name} ({command.desc})")
|
||||
res = command.redo()
|
||||
return self, res
|
||||
|
||||
if current.attrs.on_undo is not None:
|
||||
return self, current.attrs.on_redo()
|
||||
else:
|
||||
return self
|
||||
|
||||
def refresh(self):
|
||||
return self.__ft__(oob=True)
|
||||
@@ -83,7 +115,7 @@ class UndoRedo(BaseComponentSingleton):
|
||||
return mk_tooltip(mk_icon(icon_undo,
|
||||
size=24,
|
||||
**self._commands.undo()),
|
||||
f"Undo '{command.name}'.")
|
||||
f"Undo '{command.attrs.name}'.")
|
||||
else:
|
||||
return mk_tooltip(mk_icon(icon_undo,
|
||||
size=24,
|
||||
@@ -97,7 +129,7 @@ class UndoRedo(BaseComponentSingleton):
|
||||
return mk_tooltip(mk_icon(icon_redo,
|
||||
size=24,
|
||||
**self._commands.redo()),
|
||||
f"Redo '{command.name}'.")
|
||||
f"Redo '{command.attrs.name}'.")
|
||||
else:
|
||||
return mk_tooltip(mk_icon(icon_redo,
|
||||
size=24,
|
||||
@@ -106,7 +138,7 @@ class UndoRedo(BaseComponentSingleton):
|
||||
"Nothing to redo.")
|
||||
|
||||
def _can_undo(self):
|
||||
return self.index >= 0
|
||||
return self.index >= 1
|
||||
|
||||
def _can_redo(self):
|
||||
return self.index < len(self.history) - 1
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
UNDO_REDO_INSTANCE_ID = "__UndoRedo__"
|
||||
|
||||
ROUTE_ROOT = "/undo"
|
||||
@@ -5,4 +8,16 @@ ROUTE_ROOT = "/undo"
|
||||
|
||||
class Routes:
|
||||
Undo = "/undo"
|
||||
Redo = "/redo"
|
||||
Redo = "/redo"
|
||||
|
||||
|
||||
@dataclass
|
||||
class UndoRedoAttrs:
|
||||
name: str
|
||||
desc: str = None
|
||||
on_undo: Callable = None
|
||||
on_redo: Callable = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.on_redo is None:
|
||||
self.on_redo = self.on_undo
|
||||
Reference in New Issue
Block a user