Live undo/redo: add/delete component and link, move, property edits
Undo and redo now work against a connected device; each step is sent there through Sox. Deleting a component first reads its whole subtree back (config, unloaded children, links, place among siblings) so undo can recreate it; recreated components take new device ids, which the DOM elements pick up so later steps still reach them. Binary Buf config (a user's cred) is read with read_prop, since the cache decodes every Buf as a Str. Moves, tidy layout, and Properties / page editor / user edits record only what the device accepted. A step that does not fully reach the device warns and clears the history. History is also cleared on every view change in live mode, as opening a sheet rebuilds the DOM from the device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -531,27 +531,27 @@ def _clear_restoring_flag():
|
||||
|
||||
|
||||
def do_undo(event=None):
|
||||
if app_state.live_mode:
|
||||
app_state.set_status("Undo is not available in live mode", warn=True)
|
||||
return "break"
|
||||
label = undo.undo()
|
||||
if label is None:
|
||||
return "break"
|
||||
app_state.window.title(f"{app_state.base_window_title()} — undid {label}")
|
||||
app_state.window.title(f"{_base_title()} — undid {label}")
|
||||
return "break"
|
||||
|
||||
|
||||
def do_redo(event=None):
|
||||
if app_state.live_mode:
|
||||
app_state.set_status("Redo is not available in live mode", warn=True)
|
||||
return "break"
|
||||
label = undo.redo()
|
||||
if label is None:
|
||||
return "break"
|
||||
app_state.window.title(f"{app_state.base_window_title()} — redid {label}")
|
||||
app_state.window.title(f"{_base_title()} — redid {label}")
|
||||
return "break"
|
||||
|
||||
|
||||
def _base_title():
|
||||
if app_state.live_mode:
|
||||
return app_state.base_window_title_live()
|
||||
return app_state.base_window_title()
|
||||
|
||||
|
||||
def do_save(event=None):
|
||||
if app_state.live_mode:
|
||||
do_download_to_sax()
|
||||
@@ -1771,11 +1771,12 @@ def on_canvas_release(event):
|
||||
"Move", f"{comp_element.get('name', 'The component')} was not "
|
||||
"moved on the device")
|
||||
return
|
||||
if not app_state.live_mode:
|
||||
undo.push(undo.MetaMoveCommand(
|
||||
[(comp_element, meta_before, raw_x, raw_y)],
|
||||
label=f"move {comp_element.get('name', 'component')}"
|
||||
))
|
||||
command_class = (undo.LiveMetaMoveCommand if app_state.live_mode
|
||||
else undo.MetaMoveCommand)
|
||||
undo.push(command_class(
|
||||
[(comp_element, meta_before, raw_x, raw_y)],
|
||||
label=f"move {comp_element.get('name', 'component')}"
|
||||
))
|
||||
app_state.mark_dirty()
|
||||
|
||||
app_state.drag_data["tag"] = None
|
||||
@@ -2029,6 +2030,12 @@ def record_visit(kind, element):
|
||||
"""
|
||||
global history_index, _history_file_path
|
||||
|
||||
# On a device, undo only reaches back as far as the view that is open. Opening a
|
||||
# sheet reads the DOM afresh from the device, so steps taken before it name elements
|
||||
# that are no longer there; Back and Forward are view changes too.
|
||||
if app_state.live_mode:
|
||||
undo.reset()
|
||||
|
||||
if _suppress_history:
|
||||
return
|
||||
|
||||
@@ -2655,6 +2662,8 @@ def _live_tree_expand(comp_id, tree_item_id):
|
||||
def _ui():
|
||||
global _restoring_selection
|
||||
app_state.xml_root_element = sax_root
|
||||
# A fresh DOM: undo steps name elements of the one it replaces.
|
||||
undo.reset()
|
||||
sax_file.refresh_treeview_from_dom(sax_root)
|
||||
# Re-open and re-select the node without triggering a wiresheet
|
||||
# re-render — the sheet is already showing the right content and
|
||||
|
||||
Reference in New Issue
Block a user