From 0c08dfb13d46ec3dc277003b61137c0f3ef0ddb2 Mon Sep 17 00:00:00 2001 From: Dream <42954461+eureka0928@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:30:47 -0400 Subject: [PATCH] :sparkles: Add the ability for save and restore selection state in undo/redo (#8652) * :sparkles: Capture selection state before changes are applied Save current selection IDs in commit-changes so undo entries can track what was selected before each action. * :sparkles: Save and restore selection state in undo/redo Extend undo entry with selected-before and selected-after fields. On undo, restore selection to what it was before the action. On redo, restore selection to what it was after the action. Handles single entries, stacked entries, accumulated transactions, and undo groups. Fixes #6007 * :recycle: Wire selected-before through workspace undo stream Pass the captured selection state from commit data into the undo entry so it is stored alongside changes. * :bug: Fix unmatched delimiter in changes.cljs * :bug: Pass selected-before through commit event to undo entry selected-before was captured in commit-changes but dropped by the commit function since it was missing from the destructuring and the commit map. This caused restore-selection to receive nil on undo. --------- Signed-off-by: eureka928 Co-authored-by: Mihai --- frontend/src/app/main/data/changes.cljs | 32 +++++----- frontend/src/app/main/data/workspace.cljs | 5 +- .../src/app/main/data/workspace/undo.cljs | 58 +++++++++++++------ 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index 7cae1add0f..042f943e2c 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -122,7 +122,8 @@ (defn commit "Create a commit event instance" [{:keys [commit-id redo-changes undo-changes origin save-undo? features - file-id file-revn file-vern undo-group tags stack-undo? source ignore-wasm?]}] + file-id file-revn file-vern undo-group tags stack-undo? source ignore-wasm? + selected-before]}] (assert (cpc/check-changes redo-changes) "expect valid vector of changes for redo-changes") @@ -148,7 +149,8 @@ :undo-group undo-group :tags tags :stack-undo? stack-undo? - :ignore-wasm? ignore-wasm?}] + :ignore-wasm? ignore-wasm? + :selected-before selected-before}] (ptk/reify ::commit cljs.core/IDeref @@ -205,15 +207,17 @@ ;; Prevent commit changes by a viewer team member (it really should never happen) (when (:can-edit permissions) - (rx/of (-> params - (assoc :undo-group undo-group) - (assoc :features features) - (assoc :tags tags) - (assoc :stack-undo? stack-undo?) - (assoc :save-undo? save-undo?) - (assoc :file-id file-id) - (assoc :file-revn (resolve-file-revn state file-id)) - (assoc :file-vern (resolve-file-vern state file-id)) - (assoc :undo-changes uchg) - (assoc :redo-changes rchg) - (commit)))))))) + (let [selected (dm/get-in state [:workspace-local :selected])] + (rx/of (-> params + (assoc :undo-group undo-group) + (assoc :features features) + (assoc :tags tags) + (assoc :stack-undo? stack-undo?) + (assoc :save-undo? save-undo?) + (assoc :file-id file-id) + (assoc :file-revn (resolve-file-revn state file-id)) + (assoc :file-vern (resolve-file-vern state file-id)) + (assoc :undo-changes uchg) + (assoc :redo-changes rchg) + (assoc :selected-before selected) + (commit))))))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index dd03d7601e..35677f3785 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -484,12 +484,13 @@ (rx/filter dch/commit?) (rx/map deref) (rx/mapcat - (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}] + (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo? selected-before]}] (if (and save-undo? (seq undo-changes)) (let [entry {:undo-changes undo-changes :redo-changes redo-changes :undo-group undo-group - :tags tags}] + :tags tags + :selected-before selected-before}] (rx/of (dwu/append-undo entry stack-undo?))) (rx/empty)))))) (rx/take-until stoper-s)))) diff --git a/frontend/src/app/main/data/workspace/undo.cljs b/frontend/src/app/main/data/workspace/undo.cljs index 2b2c6f048b..2296aed447 100644 --- a/frontend/src/app/main/data/workspace/undo.cljs +++ b/frontend/src/app/main/data/workspace/undo.cljs @@ -60,7 +60,9 @@ [:undo-changes [:vector cpc/schema:change]] [:redo-changes [:vector cpc/schema:change]] [:undo-group ::sm/uuid] - [:tags [:set :keyword]]]) + [:tags [:set :keyword]] + [:selected-before {:optional true} [:maybe [:set ::sm/uuid]]] + [:selected-after {:optional true} [:maybe [:set ::sm/uuid]]]]) (def check-undo-entry (sm/check-fn schema:undo-entry)) @@ -103,24 +105,28 @@ (defn- stack-undo-entry "Extends the current undo entry in the workspace with new changes if it exists, or creates a new entry if it doesn't." - [state {:keys [undo-changes redo-changes] :as entry}] + [state {:keys [undo-changes redo-changes selected-after] :as entry}] (let [index (get-in state [:workspace-undo :index] -1)] (if (>= index 0) (update-in state [:workspace-undo :items index] (fn [item] (-> item (update :undo-changes #(into undo-changes %)) - (update :redo-changes #(into % redo-changes))))) + (update :redo-changes #(into % redo-changes)) + (assoc :selected-after selected-after)))) (add-undo-entry state entry)))) (defn- accumulate-undo-entry "Extends the current undo transaction with new changes." - [state {:keys [undo-changes redo-changes undo-group tags]}] + [state {:keys [undo-changes redo-changes undo-group tags selected-before selected-after]}] (-> state (update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %)) (update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes)) (cond-> (nil? (get-in state [:workspace-undo :transaction :undo-group])) (assoc-in [:workspace-undo :transaction :undo-group] undo-group)) + (cond-> (nil? (get-in state [:workspace-undo :transaction :selected-before])) + (assoc-in [:workspace-undo :transaction :selected-before] selected-before)) + (assoc-in [:workspace-undo :transaction :selected-after] selected-after) (assoc-in [:workspace-undo :transaction :tags] tags))) (defn append-undo @@ -137,18 +143,20 @@ (ptk/reify ::append-undo ptk/UpdateEvent (update [_ state] - (cond - (and (get-in state [:workspace-undo :transaction]) - (or (not stack?) - (d/not-empty? (get-in state [:workspace-undo :transaction :undo-changes])) - (d/not-empty? (get-in state [:workspace-undo :transaction :redo-changes])))) - (accumulate-undo-entry state entry) + (let [selected-after (dm/get-in state [:workspace-local :selected]) + entry (assoc entry :selected-after selected-after)] + (cond + (and (get-in state [:workspace-undo :transaction]) + (or (not stack?) + (d/not-empty? (get-in state [:workspace-undo :transaction :undo-changes])) + (d/not-empty? (get-in state [:workspace-undo :transaction :redo-changes])))) + (accumulate-undo-entry state entry) - stack? - (stack-undo-entry state entry) + stack? + (stack-undo-entry state entry) - :else - (add-undo-entry state entry))))) + :else + (add-undo-entry state entry)))))) (def empty-tx {:undo-changes [] :redo-changes []}) @@ -234,6 +242,16 @@ (rx/map first) (rx/map commit-undo-transaction)))))) +(defn- restore-selection + "Restores the selection state from an undo entry." + [selected-ids] + (ptk/reify ::restore-selection + ptk/UpdateEvent + (update [_ state] + (if (some? selected-ids) + (assoc-in state [:workspace-local :selected] selected-ids) + state)))) + (defn undo-to-index "Repeat undoing or redoing until dest-index is reached." [dest-index] @@ -302,12 +320,15 @@ (find-first-group-idx index))] (if undo-group - (rx/of (undo-to-index (dec undo-group-index))) + (let [first-item (get items undo-group-index)] + (rx/of (undo-to-index (dec undo-group-index)) + (restore-selection (:selected-before first-item)))) (rx/of (materialize-undo changes (dec index)) (dch/commit-changes {:redo-changes changes :undo-changes [] :save-undo? false :origin it}) + (restore-selection (:selected-before item)) (assure-valid-current-page))))))))))) (def redo @@ -337,12 +358,15 @@ redo-group-index (when undo-group (find-last-group-idx (inc index)))] (if undo-group - (rx/of (undo-to-index redo-group-index)) + (let [last-item (get items redo-group-index)] + (rx/of (undo-to-index redo-group-index) + (restore-selection (:selected-after last-item)))) (rx/of (materialize-undo changes (inc index)) (dch/commit-changes {:redo-changes changes :undo-changes [] :origin it - :save-undo? false}))))))))))) + :save-undo? false}) + (restore-selection (:selected-after item)))))))))))) (defn- assure-valid-current-page []