Skip to content
Snippets Groups Projects

Refactor Cart into checkerboard table

Merged Mateusz Kudela requested to merge mkudela/issue-173 into develop
Compare and Show latest version
1 file
+ 89
52
Compare changes
  • Side-by-side
  • Inline
@@ -10,6 +10,7 @@ from textual.reactive import reactive
from textual.widgets import Static
from clive.__private.core.formatters.humanize import humanize_operation_details, humanize_operation_name
from clive.__private.logger import logger
from clive.__private.ui.get_css import get_relative_css_path
from clive.__private.ui.shared.base_screen import BaseScreen
from clive.__private.ui.transaction_summary import TransactionSummaryFromCart
@@ -60,15 +61,23 @@ class CartItem(CliveCheckerboardTableRow, CliveWidget, can_focus=True):
Binding("ctrl+down", "select_next", "Next"),
]
class RemovalCheck(Message):
"""Message for checking if removal is in progress."""
def __init__(self, widget: CartItem) -> None:
self.widget = widget
super().__init__()
class Delete(Message):
def __init__(self, widget: CartItem) -> None:
self.widget = widget
super().__init__()
class Move(Message):
def __init__(self, from_idx: int, to_idx: int) -> None:
def __init__(self, from_idx: int, to_idx: int, focus_button: str | None) -> None:
self.from_idx = from_idx
self.to_idx = to_idx
self.focus_button = focus_button
super().__init__()
class Focus(Message):
@@ -79,10 +88,13 @@ class CartItem(CliveCheckerboardTableRow, CliveWidget, can_focus=True):
self.focus_button = focus_button
super().__init__()
class StartRemovalProcess(Message):
"""Message to prevent sending duplicated Delete message."""
def __init__(self, operation_idx: int) -> None:
self.__idx = operation_idx
self._idx = operation_idx
assert self.is_valid(self.__idx), "During construction, index has to be valid"
assert self.is_valid(self._idx), "During construction, index has to be valid"
self._index_cell = CliveCheckerBoardTableCell(self.get_operation_index(), classes="index")
super().__init__(
self._index_cell,
@@ -95,12 +107,13 @@ class CartItem(CliveCheckerboardTableRow, CliveWidget, can_focus=True):
classes="actions",
),
)
self._ready_for_deletion = False
def __repr__(self) -> str:
return f"{self.__class__.__name__}(idx={self.__idx})"
return f"{self.__class__.__name__}(idx={self._idx})"
def on_mount(self) -> None:
self.reactive_idx = self.__idx
self.reactive_idx = self._idx
if self.__is_first:
self.unbind("ctrl+up")
elif self.__is_last:
@@ -108,25 +121,25 @@ class CartItem(CliveCheckerboardTableRow, CliveWidget, can_focus=True):
def watch_reactive_idx(self, idx: int) -> None:
assert self.is_valid(idx), "idx is invalid when trying to update."
self.__idx = idx
self._idx = idx
self._index_cell.update_content(self.get_operation_index())
def is_valid(self, idx: int) -> bool:
return idx < self.__operations_count
def get_operation_index(self) -> str:
return f"{self.__idx + 1}." if self.is_valid(self.idx) else "?"
return f"{self._idx + 1}." if self.is_valid(self.idx) else "?"
def get_operation_name(self) -> str:
return humanize_operation_name(self.operation) if self.is_valid(self.idx) else "?"
def action_select_previous(self) -> None:
focused = self.get_focused_button_or_none()
self.post_message(self.Focus(target_idx=self.__idx - 1, focus_button=focused))
self.post_message(self.Focus(target_idx=self._idx - 1, focus_button=focused))
def action_select_next(self) -> None:
focused = self.get_focused_button_or_none()
self.post_message(self.Focus(target_idx=self.__idx + 1, focus_button=focused))
self.post_message(self.Focus(target_idx=self._idx + 1, focus_button=focused))
def get_operation_details(self) -> str:
return humanize_operation_details(self.operation) if self.is_valid(self.idx) else "?"
@@ -140,12 +153,20 @@ class CartItem(CliveCheckerboardTableRow, CliveWidget, can_focus=True):
@property
def idx(self) -> int:
return self.__idx
return self._idx
@property
def operation(self) -> OperationBaseClass:
assert self.is_valid(self.idx), "cannot get operation, position is invalid"
return self.profile.cart[self.__idx]
assert self.is_valid(self._idx), "cannot get operation, position is invalid"
return self.profile.cart[self._idx]
@property
def ready_for_deletion(self) -> bool:
return self._ready_for_deletion
@ready_for_deletion.setter
def ready_for_deletion(self, value: bool) -> None:
self._ready_for_deletion = value
@property
def __operations_count(self) -> int:
@@ -153,32 +174,34 @@ class CartItem(CliveCheckerboardTableRow, CliveWidget, can_focus=True):
@property
def __is_first(self) -> bool:
return self.__idx == 0
return self._idx == 0
@property
def __is_last(self) -> bool:
return self.__idx == self.__operations_count - 1
return self._idx == self.__operations_count - 1
@on(CliveButton.Pressed, "#move-up-button")
def move_up(self) -> None:
self.post_message(self.Move(from_idx=self.__idx, to_idx=self.__idx - 1))
focused = self.get_focused_button_or_none()
self.post_message(self.Move(from_idx=self._idx, to_idx=self._idx - 1, focus_button=focused))
@on(CliveButton.Pressed, "#move-down-button")
def move_down(self) -> None:
self.post_message(self.Move(from_idx=self.__idx, to_idx=self.__idx + 1))
focused = self.get_focused_button_or_none()
self.post_message(self.Move(from_idx=self._idx, to_idx=self._idx + 1, focus_button=focused))
@on(CliveButton.Pressed, "#delete-button")
def delete(self) -> None:
cart = self.app.query_one(Cart)
if cart.ready_to_delete_item:
cart.toggle_removal_in_progress()
self.post_message(self.RemovalCheck(self))
logger.debug(f"DELETE CLICKED, READY?: {self._ready_for_deletion}")
if self._ready_for_deletion:
self.post_message(self.StartRemovalProcess())
self.post_message(self.Delete(self))
@on(Move)
def move_item(self, event: CartItem.Move) -> None:
if event.to_idx == self.__idx:
self.__idx = event.from_idx
if event.to_idx == self._idx:
self._idx = event.from_idx
self.app.trigger_profile_watchers()
@@ -195,6 +218,11 @@ class CartTable(CliveCheckerboardTable):
def __init__(self) -> None:
super().__init__(header=CartHeader(), title=Static(""))
self._removal_in_progress = False
@property
def ready_to_delete_item(self) -> bool:
return not self._removal_in_progress
def create_static_rows(self, start_index: int = 0, end_index: int | None = None) -> list[CartItem]:
if end_index:
@@ -204,36 +232,13 @@ class CartTable(CliveCheckerboardTable):
for idx in range(start_index, len(self.app.world.profile.cart) if end_index is None else end_index + 1)
]
def _start_removal_process(self) -> None:
self._removal_in_progress = True
class Cart(BaseScreen):
CSS_PATH = [get_relative_css_path(__file__)]
BINDINGS = [
Binding("escape", "app.pop_screen", "Back"),
Binding("f9", "clear_all", "Clear all"),
Binding("f6", "summary", "Summary"),
]
BIG_TITLE = "operations cart"
def __init__(self) -> None:
super().__init__()
self.__scrollable_part = ScrollablePart()
self._cart_table = CartTable()
def _end_removal_process(self) -> None:
self.app.trigger_profile_watchers()
self._removal_in_progress = False
@property
def ready_to_delete_item(self) -> bool:
return not self._removal_in_progress
def create_main_panel(self) -> ComposeResult:
with self.__scrollable_part:
yield self._cart_table
def toggle_removal_in_progress(self) -> None:
self._removal_in_progress = not self._removal_in_progress
async def __rebuild_items(self, from_index: int = 0, to_index: int | None = None) -> None:
await self._cart_table.rebuild(starting_from_element=from_index, ending_with_element=to_index)
def _handle_remove_event(self, triggering_widget: CartItem) -> None:
def devalue_indexes(rows: Sequence[CartItem]) -> None:
for row in rows:
@@ -242,7 +247,7 @@ class Cart(BaseScreen):
start_index = triggering_widget.reactive_idx
rows = self.query(CartItem)[start_index:]
devalue_indexes(rows)
self._cart_table.set_evenness_styles(rows, starting_index=start_index)
self.set_evenness_styles(rows, starting_index=start_index)
def _update_values_of_swapped_rows(self, from_index: int, to_index: int) -> None:
def get_cells_with_values(row_index: int) -> tuple[list[CliveCheckerBoardTableCell], list[str | Widget]]:
@@ -260,13 +265,19 @@ class Cart(BaseScreen):
for cell, value in zip(cells, data):
cell.update_content(value) # type: ignore[arg-type]
@on(CartItem.RemovalCheck)
def check_removal(self, event: CartItem.RemovalCheck) -> None:
event.widget.ready_for_deletion = self.ready_to_delete_item
@on(CartItem.StartRemovalProcess)
def start_removal_process(self) -> None:
self._start_removal_process()
@on(CartItem.Delete)
async def remove_item(self, event: CartItem.Delete) -> None:
await self.query(CliveCheckerboardTableRow)[event.widget.reactive_idx].remove()
self.profile.cart.remove(event.widget.operation)
self._handle_remove_event(triggering_widget=event.widget)
self.app.trigger_profile_watchers()
self._removal_in_progress = False
if len(self.app.world.profile.cart) > 0 and event.widget.reactive_idx == 0:
# disable first ButtomMoveUp if first element was removed
@@ -275,6 +286,8 @@ class Cart(BaseScreen):
# disable last ButtonMoveDown if only last element was removed
self.query(ButtonMoveDown)[-1].disabled = True
self._end_removal_process()
@on(CartItem.Move)
async def move_item(self, event: CartItem.Move) -> None:
assert event.to_idx >= 0, "Item cannot be moved to id lower than 0."
@@ -287,6 +300,8 @@ class Cart(BaseScreen):
for cart_item in self.query(CartItem):
if event.to_idx == cart_item.reactive_idx:
self.app.set_focus(cart_item)
if event.focus_button:
cart_item.query_one(f"#{event.focus_button}").focus()
@on(CartItem.Focus)
def focus_item(self, event: CartItem.Focus) -> None:
@@ -296,6 +311,28 @@ class Cart(BaseScreen):
if event.focus_button:
cart_item.query_one(f"#{event.focus_button}").focus()
class Cart(BaseScreen):
CSS_PATH = [get_relative_css_path(__file__)]
BINDINGS = [
Binding("escape", "app.pop_screen", "Back"),
Binding("f9", "clear_all", "Clear all"),
Binding("f6", "summary", "Summary"),
]
BIG_TITLE = "operations cart"
def __init__(self) -> None:
super().__init__()
self.__scrollable_part = ScrollablePart()
self._cart_table = CartTable()
def create_main_panel(self) -> ComposeResult:
with self.__scrollable_part:
yield self._cart_table
async def __rebuild_items(self, from_index: int = 0, to_index: int | None = None) -> None:
await self._cart_table.rebuild(starting_from_element=from_index, ending_with_element=to_index)
def action_summary(self) -> None:
self.app.push_screen(TransactionSummaryFromCart())
Loading