Working on undo redo capabilities
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
--datagrid-resize-zindex: 1;
|
--datagrid-resize-zindex: 1;
|
||||||
--color-splitter: color-mix(in oklab, var(--color-base-content) 50%, #0000);
|
--color-splitter: color-mix(in oklab, var(--color-base-content) 50%, #0000);
|
||||||
--color-splitter-active: color-mix(in oklab, var(--color-base-content) 50%, #ffff);
|
--color-splitter-active: color-mix(in oklab, var(--color-base-content) 50%, #ffff);
|
||||||
|
--color-btn-hover: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mmt-tooltip-container {
|
.mmt-tooltip-container {
|
||||||
@@ -36,6 +37,19 @@
|
|||||||
transition: opacity 0.2s ease, visibility 0s linear 0.2s;
|
transition: opacity 0.2s ease, visibility 0s linear 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mmt-btn {
|
||||||
|
user-select: none;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mmt-btn:hover {
|
||||||
|
background-color: var(--color-btn-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mmt-btn-disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
/*cursor: not-allowed;*/
|
||||||
|
}
|
||||||
|
|
||||||
/* When parent is hovered, show the child elements with this class */
|
/* When parent is hovered, show the child elements with this class */
|
||||||
*:hover > .mmt-visible-on-hover {
|
*:hover > .mmt-visible-on-hover {
|
||||||
@@ -63,6 +77,8 @@
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-24 svg {
|
.icon-24 svg {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from components.drawerlayout.assets.icons import icon_panel_contract_regular, ic
|
|||||||
from components.drawerlayout.constants import DRAWER_LAYOUT_INSTANCE_ID
|
from components.drawerlayout.constants import DRAWER_LAYOUT_INSTANCE_ID
|
||||||
from components.repositories.components.Repositories import Repositories
|
from components.repositories.components.Repositories import Repositories
|
||||||
from components.tabs.components.MyTabs import MyTabs
|
from components.tabs.components.MyTabs import MyTabs
|
||||||
|
from components.undo_redo.components.UndoRedo import UndoRedo
|
||||||
from components.workflows.components.Workflows import Workflows
|
from components.workflows.components.Workflows import Workflows
|
||||||
from core.instance_manager import InstanceManager
|
from core.instance_manager import InstanceManager
|
||||||
from core.settings_management import SettingsManager
|
from core.settings_management import SettingsManager
|
||||||
@@ -31,6 +32,7 @@ class DrawerLayout(BaseComponent):
|
|||||||
self._ai_buddy = self._create_component(AIBuddy)
|
self._ai_buddy = self._create_component(AIBuddy)
|
||||||
self._admin = self._create_component(Admin)
|
self._admin = self._create_component(Admin)
|
||||||
self._applications = self._create_component(Applications)
|
self._applications = self._create_component(Applications)
|
||||||
|
self._undo_redo = self._create_component(UndoRedo)
|
||||||
|
|
||||||
self.top_components = self._get_sub_components("TOP", [self._ai_buddy])
|
self.top_components = self._get_sub_components("TOP", [self._ai_buddy])
|
||||||
self.bottom_components = self._get_sub_components("BOTTOM", [self._ai_buddy])
|
self.bottom_components = self._get_sub_components("BOTTOM", [self._ai_buddy])
|
||||||
@@ -53,12 +55,16 @@ class DrawerLayout(BaseComponent):
|
|||||||
name="sidebar"
|
name="sidebar"
|
||||||
),
|
),
|
||||||
Div(
|
Div(
|
||||||
Label(
|
Div(
|
||||||
Input(type="checkbox",
|
Label(
|
||||||
onclick=f"document.getElementById('sidebar_{self._id}').classList.toggle('collapsed');"),
|
Input(type="checkbox",
|
||||||
icon_panel_contract_regular,
|
onclick=f"document.getElementById('sidebar_{self._id}').classList.toggle('collapsed');"),
|
||||||
icon_panel_expand_regular,
|
icon_panel_contract_regular,
|
||||||
cls="swap",
|
icon_panel_expand_regular,
|
||||||
|
cls="swap mr-4",
|
||||||
|
),
|
||||||
|
self._undo_redo,
|
||||||
|
cls="flex"
|
||||||
),
|
),
|
||||||
Div(*[component for component in self.top_components], name="top", cls='dl-top'),
|
Div(*[component for component in self.top_components], name="top", cls='dl-top'),
|
||||||
Div(self._tabs, id=f"page_{self._id}", name="page", cls='dl-page'),
|
Div(self._tabs, id=f"page_{self._id}", name="page", cls='dl-page'),
|
||||||
|
|||||||
23
src/components/undo_redo/UndoRedoApp.py
Normal file
23
src/components/undo_redo/UndoRedoApp.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from fasthtml.fastapp import fast_app
|
||||||
|
|
||||||
|
from components.undo_redo.constants import Routes
|
||||||
|
from core.instance_manager import debug_session, InstanceManager
|
||||||
|
|
||||||
|
logger = logging.getLogger("UndoRedoApp")
|
||||||
|
|
||||||
|
undo_redo_app, rt = fast_app()
|
||||||
|
|
||||||
|
|
||||||
|
@rt(Routes.Undo)
|
||||||
|
def post(session, _id: str):
|
||||||
|
logger.debug(f"Entering {Routes.Undo} with args {debug_session(session)}, {_id=}")
|
||||||
|
instance = InstanceManager.get(session, _id)
|
||||||
|
return instance.undo()
|
||||||
|
|
||||||
|
@rt(Routes.Redo)
|
||||||
|
def post(session, _id: str):
|
||||||
|
logger.debug(f"Entering {Routes.Redo} with args {debug_session(session)}, {_id=}")
|
||||||
|
instance = InstanceManager.get(session, _id)
|
||||||
|
return instance.redo()
|
||||||
0
src/components/undo_redo/__init__.py
Normal file
0
src/components/undo_redo/__init__.py
Normal file
0
src/components/undo_redo/assets/__init__.py
Normal file
0
src/components/undo_redo/assets/__init__.py
Normal file
7
src/components/undo_redo/assets/icons.py
Normal file
7
src/components/undo_redo/assets/icons.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from fastcore.basics import NotStr
|
||||||
|
|
||||||
|
# carbon Undo
|
||||||
|
icon_undo = NotStr("""<svg name="undo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><path d="M20 10H7.815l3.587-3.586L10 5l-6 6l6 6l1.402-1.415L7.818 12H20a6 6 0 0 1 0 12h-8v2h8a8 8 0 0 0 0-16z" fill="currentColor"></path></svg>""")
|
||||||
|
|
||||||
|
# carbon Redo
|
||||||
|
icon_redo = NotStr("""<svg name="redo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><path d="M12 10h12.185l-3.587-3.586L22 5l6 6l-6 6l-1.402-1.415L24.182 12H12a6 6 0 0 0 0 12h8v2h-8a8 8 0 0 1 0-16z" fill="currentColor"></path></svg>""")
|
||||||
25
src/components/undo_redo/commands.py
Normal file
25
src/components/undo_redo/commands.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from components.BaseCommandManager import BaseCommandManager
|
||||||
|
from components.undo_redo.constants import ROUTE_ROOT, Routes
|
||||||
|
|
||||||
|
|
||||||
|
class UndoRedoCommandManager(BaseCommandManager):
|
||||||
|
def __init__(self, owner):
|
||||||
|
super().__init__(owner)
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
return {
|
||||||
|
"hx-post": f"{ROUTE_ROOT}{Routes.Undo}",
|
||||||
|
"hx-trigger": "click, keyup[ctrlKey&&key=='z'] from:body",
|
||||||
|
"hx-target": f"#{self._id}",
|
||||||
|
"hx-swap": "innerHTML",
|
||||||
|
"hx-vals": f'{{"_id": "{self._id}"}}',
|
||||||
|
}
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
return {
|
||||||
|
"hx-post": f"{ROUTE_ROOT}{Routes.Redo}",
|
||||||
|
"hx_trigger": "click, keyup[ctrlKey&&key=='y'] from:body",
|
||||||
|
"hx-target": f"#{self._id}",
|
||||||
|
"hx-swap": "innerHTML",
|
||||||
|
"hx-vals": f'{{"_id": "{self._id}"}}',
|
||||||
|
}
|
||||||
89
src/components/undo_redo/components/UndoRedo.py
Normal file
89
src/components/undo_redo/components/UndoRedo.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import logging
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
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_helpers import mk_icon
|
||||||
|
|
||||||
|
logger = logging.getLogger("UndoRedoApp")
|
||||||
|
|
||||||
|
|
||||||
|
class CommandHistory(ABC):
|
||||||
|
def __init__(self, name, desc):
|
||||||
|
self.name = name
|
||||||
|
self.desc = desc
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def redo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UndoRedo(BaseComponentSingleton):
|
||||||
|
COMPONENT_INSTANCE_ID = UNDO_REDO_INSTANCE_ID
|
||||||
|
|
||||||
|
def __init__(self, session, _id, settings_manager=None, tabs_manager=None):
|
||||||
|
super().__init__(session, _id, settings_manager, tabs_manager)
|
||||||
|
self.index = 0
|
||||||
|
self.history = []
|
||||||
|
self._commands = UndoRedoCommandManager(self)
|
||||||
|
|
||||||
|
def push(self, command: CommandHistory):
|
||||||
|
self.history.append(command)
|
||||||
|
self.index += 1
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
logger.info(f"Undo command")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
logger.info("Redo command")
|
||||||
|
if self.index > 0:
|
||||||
|
self.history.clear()
|
||||||
|
self.index = 0
|
||||||
|
else:
|
||||||
|
self.push("something")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __ft__(self):
|
||||||
|
return Div(
|
||||||
|
self._mk_undo(),
|
||||||
|
self._mk_redo(),
|
||||||
|
id=self._id,
|
||||||
|
cls="flex"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _mk_undo(self):
|
||||||
|
if self._can_undo():
|
||||||
|
return mk_icon(icon_undo,
|
||||||
|
size=24,
|
||||||
|
**self._commands.undo())
|
||||||
|
else:
|
||||||
|
return mk_icon(icon_undo,
|
||||||
|
size=24,
|
||||||
|
can_select=False,
|
||||||
|
cls="mmt-btn-disabled")
|
||||||
|
|
||||||
|
def _mk_redo(self):
|
||||||
|
if self._can_redo():
|
||||||
|
return mk_icon(icon_redo,
|
||||||
|
size=24,
|
||||||
|
**self._commands.redo())
|
||||||
|
else:
|
||||||
|
return mk_icon(icon_redo,
|
||||||
|
size=24,
|
||||||
|
can_select=False,
|
||||||
|
cls="mmt-btn-disabled")
|
||||||
|
|
||||||
|
def _can_undo(self):
|
||||||
|
return self.index > 0
|
||||||
|
|
||||||
|
def _can_redo(self):
|
||||||
|
return self.index < len(self.history) - 1
|
||||||
0
src/components/undo_redo/components/__init__.py
Normal file
0
src/components/undo_redo/components/__init__.py
Normal file
8
src/components/undo_redo/constants.py
Normal file
8
src/components/undo_redo/constants.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
UNDO_REDO_INSTANCE_ID = "__UndoRedo__"
|
||||||
|
|
||||||
|
ROUTE_ROOT = "/undo"
|
||||||
|
|
||||||
|
|
||||||
|
class Routes:
|
||||||
|
Undo = "/undo"
|
||||||
|
Redo = "/redo"
|
||||||
@@ -3,9 +3,10 @@ from fasthtml.components import *
|
|||||||
from core.utils import merge_classes
|
from core.utils import merge_classes
|
||||||
|
|
||||||
|
|
||||||
def mk_icon(icon, size=20, can_select=True, cls='', tooltip=None, **kwargs):
|
def mk_icon(icon, size=20, can_select=True, can_hover=False, cls='', tooltip=None, **kwargs):
|
||||||
merged_cls = merge_classes(f"icon-{size}",
|
merged_cls = merge_classes(f"icon-{size}",
|
||||||
'icon-btn' if can_select else '',
|
'icon-btn' if can_select else '',
|
||||||
|
'mmt-btn' if can_hover else '',
|
||||||
cls,
|
cls,
|
||||||
kwargs)
|
kwargs)
|
||||||
return mk_tooltip(icon, tooltip, cls=merged_cls, **kwargs) if tooltip else Div(icon, cls=merged_cls, **kwargs)
|
return mk_tooltip(icon, tooltip, cls=merged_cls, **kwargs) if tooltip else Div(icon, cls=merged_cls, **kwargs)
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ register_component("login", "components.login", "LoginApp")
|
|||||||
register_component("register", "components.register", "RegisterApp")
|
register_component("register", "components.register", "RegisterApp")
|
||||||
register_component("theme_controller", "components.themecontroller", "ThemeControllerApp")
|
register_component("theme_controller", "components.themecontroller", "ThemeControllerApp")
|
||||||
register_component("main_layout", "components.drawerlayout", "DrawerLayoutApp")
|
register_component("main_layout", "components.drawerlayout", "DrawerLayoutApp")
|
||||||
|
register_component("undo_redo", "components.undo_redo", "UndoRedoApp")
|
||||||
register_component("tabs", "components.tabs", "TabsApp") # before repositories
|
register_component("tabs", "components.tabs", "TabsApp") # before repositories
|
||||||
register_component("applications", "components.applications", "ApplicationsApp")
|
register_component("applications", "components.applications", "ApplicationsApp")
|
||||||
register_component("repositories", "components.repositories", "RepositoriesApp")
|
register_component("repositories", "components.repositories", "RepositoriesApp")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from components.repositories.components.Repositories import Repositories
|
from components.repositories.components.Repositories import Repositories
|
||||||
|
from components.undo_redo.components.UndoRedo import UndoRedo
|
||||||
from core.instance_manager import InstanceManager
|
from core.instance_manager import InstanceManager
|
||||||
|
|
||||||
|
|
||||||
@@ -6,4 +7,8 @@ class ComponentsInstancesHelper:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_repositories(session):
|
def get_repositories(session):
|
||||||
return InstanceManager.get(session, Repositories.create_component_id(session))
|
return InstanceManager.get(session, Repositories.create_component_id(session))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_undo_redo(session):
|
||||||
|
return InstanceManager.get(session, UndoRedo.create_component_id(session))
|
||||||
|
|
||||||
15
tests/test_undo_redo.py
Normal file
15
tests/test_undo_redo.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from components.undo_redo.components.UndoRedo import UndoRedo
|
||||||
|
from core.settings_management import SettingsManager, MemoryDbEngine
|
||||||
|
|
||||||
|
TEST_UNDO_REDO_INSTANCE_ID = "test_undo_redo_instance_id"
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def undo_redo(session, tabs_manager):
|
||||||
|
return UndoRedo(session,
|
||||||
|
UndoRedo.create_component_id(session),
|
||||||
|
settings_manager=SettingsManager(engine=MemoryDbEngine()),
|
||||||
|
tabs_manager=tabs_manager)
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_undo_and_redo(undo_redo):
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user