mirror of
https://github.com/penpot/penpot.git
synced 2025-03-30 16:41:20 -05:00
🎉 Group component sync changes in a single undo
This commit is contained in:
parent
fe898315c3
commit
ad786ab95f
7 changed files with 142 additions and 116 deletions
|
@ -39,6 +39,12 @@
|
|||
[changes stack-undo?]
|
||||
(assoc changes :stack-undo? stack-undo?))
|
||||
|
||||
(defn set-undo-group
|
||||
[changes undo-group]
|
||||
(cond-> changes
|
||||
(some? undo-group)
|
||||
(assoc :undo-group undo-group)))
|
||||
|
||||
(defn with-page
|
||||
[changes page]
|
||||
(vary-meta changes assoc
|
||||
|
@ -80,7 +86,8 @@
|
|||
[changes1 changes2]
|
||||
{:redo-changes (d/concat-vec (:redo-changes changes1) (:redo-changes changes2))
|
||||
:undo-changes (d/concat-vec (:undo-changes changes1) (:undo-changes changes2))
|
||||
:origin (:origin changes1)})
|
||||
:origin (:origin changes1)
|
||||
:undo-group (:undo-group changes1)})
|
||||
|
||||
; TODO: remove this when not needed
|
||||
(defn- assert-page-id
|
||||
|
|
|
@ -34,21 +34,20 @@
|
|||
|
||||
(declare commit-changes)
|
||||
|
||||
(defn- add-group-id
|
||||
(defn- add-undo-group
|
||||
[changes state]
|
||||
(let [undo (:workspace-undo state)
|
||||
items (:items undo)
|
||||
index (or (:index undo) (dec (count items)))
|
||||
prev-item (when-not (or (empty? items) (= index -1))
|
||||
(get items index))
|
||||
group-id (:group-id prev-item)
|
||||
add-group-id? (and
|
||||
(not (nil? group-id))
|
||||
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
|
||||
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj)) ;; This is a copy-and-move with mouse+alt
|
||||
]
|
||||
(cond-> changes add-group-id? (assoc :group-id group-id))))
|
||||
(let [undo (:workspace-undo state)
|
||||
items (:items undo)
|
||||
index (or (:index undo) (dec (count items)))
|
||||
prev-item (when-not (or (empty? items) (= index -1))
|
||||
(get items index))
|
||||
undo-group (:undo-group prev-item)
|
||||
add-undo-group? (and
|
||||
(not (nil? undo-group))
|
||||
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
|
||||
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj))] ;; This is a copy-and-move with mouse+alt
|
||||
|
||||
(cond-> changes add-undo-group? (assoc :undo-group undo-group))))
|
||||
|
||||
(def commit-changes? (ptk/type? ::commit-changes))
|
||||
|
||||
|
@ -81,7 +80,7 @@
|
|||
(pcb/set-stack-undo? stack-undo?)
|
||||
(pcb/with-objects objects))
|
||||
ids)
|
||||
changes (add-group-id changes state)]
|
||||
changes (add-undo-group changes state)]
|
||||
(rx/concat
|
||||
(if (seq (:redo-changes changes))
|
||||
(let [changes (cond-> changes reg-objects? (pcb/resize-parents ids))
|
||||
|
@ -164,15 +163,24 @@
|
|||
changes)))
|
||||
|
||||
(defn commit-changes
|
||||
"Schedules a list of changes to execute now, and add the corresponding undo changes to
|
||||
the undo stack.
|
||||
|
||||
Options:
|
||||
- save-undo?: if set to false, do not add undo changes.
|
||||
- undo-group: if some consecutive changes (or even transactions) share the same
|
||||
undo-group, they will be undone or redone in a single step
|
||||
"
|
||||
[{:keys [redo-changes undo-changes
|
||||
origin save-undo? file-id group-id stack-undo?]
|
||||
:or {save-undo? true stack-undo? false}}]
|
||||
origin save-undo? file-id undo-group stack-undo?]
|
||||
:or {save-undo? true stack-undo? false undo-group (uuid/next)}}]
|
||||
(log/debug :msg "commit-changes"
|
||||
:js/undo-group (str undo-group)
|
||||
:js/redo-changes redo-changes
|
||||
:js/undo-changes undo-changes)
|
||||
(let [error (volatile! nil)
|
||||
(let [error (volatile! nil)
|
||||
page-id (:current-page-id @st/state)
|
||||
frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))]
|
||||
frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))]
|
||||
(ptk/reify ::commit-changes
|
||||
cljs.core/IDeref
|
||||
(-deref [_]
|
||||
|
@ -183,8 +191,8 @@
|
|||
:page-id page-id
|
||||
:frames frames
|
||||
:save-undo? save-undo?
|
||||
:stack-undo? stack-undo?
|
||||
:group-id group-id})
|
||||
:undo-group undo-group
|
||||
:stack-undo? stack-undo?})
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -233,5 +241,5 @@
|
|||
(when (and save-undo? (seq undo-changes))
|
||||
(let [entry {:undo-changes undo-changes
|
||||
:redo-changes redo-changes
|
||||
:group-id group-id}]
|
||||
:undo-group undo-group}]
|
||||
(rx/of (dwu/append-undo entry stack-undo?)))))))))))
|
||||
|
|
|
@ -63,16 +63,16 @@
|
|||
(when-not (or (empty? items) (= index -1))
|
||||
(let [item (get items index)
|
||||
changes (:undo-changes item)
|
||||
group-id (:group-id item)
|
||||
undo-group (:undo-group item)
|
||||
find-first-group-idx (fn ffgidx[index]
|
||||
(let [item (get items index)]
|
||||
(if (= (:group-id item) group-id)
|
||||
(if (= (:undo-group item) undo-group)
|
||||
(ffgidx (dec index))
|
||||
(inc index))))
|
||||
|
||||
undo-group-index (when group-id
|
||||
undo-group-index (when undo-group
|
||||
(find-first-group-idx index))]
|
||||
(if group-id
|
||||
(if undo-group
|
||||
(rx/of (undo-to-index (dec undo-group-index)))
|
||||
(rx/of (dwu/materialize-undo changes (dec index))
|
||||
(dch/commit-changes {:redo-changes changes
|
||||
|
@ -94,16 +94,16 @@
|
|||
(when-not (or (empty? items) (= index (dec (count items))))
|
||||
(let [item (get items (inc index))
|
||||
changes (:redo-changes item)
|
||||
group-id (:group-id item)
|
||||
undo-group (:undo-group item)
|
||||
find-last-group-idx (fn flgidx [index]
|
||||
(let [item (get items index)]
|
||||
(if (= (:group-id item) group-id)
|
||||
(if (= (:undo-group item) undo-group)
|
||||
(flgidx (inc index))
|
||||
(dec index))))
|
||||
|
||||
redo-group-index (when group-id
|
||||
redo-group-index (when undo-group
|
||||
(find-last-group-idx (inc index)))]
|
||||
(if group-id
|
||||
(if undo-group
|
||||
(rx/of (undo-to-index redo-group-index))
|
||||
(rx/of (dwu/materialize-undo changes (inc index))
|
||||
(dch/commit-changes {:redo-changes changes
|
||||
|
|
|
@ -587,76 +587,79 @@
|
|||
NOTE: It's possible that the component to update is defined in an
|
||||
external library file, so this function may cause to modify a file
|
||||
different of that the one we are currently editing."
|
||||
[id]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::update-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(log/info :msg "UPDATE-COMPONENT of shape" :id (str id))
|
||||
(let [page-id (get state :current-page-id)
|
||||
local-file (wsh/get-local-file state)
|
||||
container (cph/get-container local-file :page page-id)
|
||||
shape (ctn/get-shape container id)]
|
||||
([id] (update-component id nil))
|
||||
([id undo-group]
|
||||
(us/assert ::us/uuid id)
|
||||
(ptk/reify ::update-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(log/info :msg "UPDATE-COMPONENT of shape" :id (str id) :undo-group undo-group)
|
||||
(let [page-id (get state :current-page-id)
|
||||
local-file (wsh/get-local-file state)
|
||||
container (cph/get-container local-file :page page-id)
|
||||
shape (ctn/get-shape container id)]
|
||||
|
||||
(when (ctk/in-component-instance? shape)
|
||||
(let [libraries (wsh/get-libraries state)
|
||||
(when (ctk/in-component-instance? shape)
|
||||
(let [libraries (wsh/get-libraries state)
|
||||
|
||||
changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-container container)
|
||||
(dwlh/generate-sync-shape-inverse libraries container id))
|
||||
changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/set-undo-group undo-group)
|
||||
(pcb/with-container container)
|
||||
(dwlh/generate-sync-shape-inverse libraries container id))
|
||||
|
||||
file-id (:component-file shape)
|
||||
file (wsh/get-file state file-id)
|
||||
file-id (:component-file shape)
|
||||
file (wsh/get-file state file-id)
|
||||
|
||||
xf-filter (comp
|
||||
(filter :local-change?)
|
||||
(map #(dissoc % :local-change?)))
|
||||
xf-filter (comp
|
||||
(filter :local-change?)
|
||||
(map #(dissoc % :local-change?)))
|
||||
|
||||
local-changes (-> changes
|
||||
(update :redo-changes #(into [] xf-filter %))
|
||||
(update :undo-changes #(into [] xf-filter %)))
|
||||
local-changes (-> changes
|
||||
(update :redo-changes #(into [] xf-filter %))
|
||||
(update :undo-changes #(into [] xf-filter %)))
|
||||
|
||||
xf-remove (comp
|
||||
(remove :local-change?)
|
||||
(map #(dissoc % :local-change?)))
|
||||
xf-remove (comp
|
||||
(remove :local-change?)
|
||||
(map #(dissoc % :local-change?)))
|
||||
|
||||
nonlocal-changes (-> changes
|
||||
(update :redo-changes #(into [] xf-remove %))
|
||||
(update :undo-changes #(into [] xf-remove %)))]
|
||||
nonlocal-changes (-> changes
|
||||
(update :redo-changes #(into [] xf-remove %))
|
||||
(update :undo-changes #(into [] xf-remove %)))]
|
||||
|
||||
(log/debug :msg "UPDATE-COMPONENT finished"
|
||||
:js/local-changes (log-changes
|
||||
(:redo-changes local-changes)
|
||||
file)
|
||||
:js/nonlocal-changes (log-changes
|
||||
(:redo-changes nonlocal-changes)
|
||||
file))
|
||||
(log/debug :msg "UPDATE-COMPONENT finished"
|
||||
:js/local-changes (log-changes
|
||||
(:redo-changes local-changes)
|
||||
file)
|
||||
:js/nonlocal-changes (log-changes
|
||||
(:redo-changes nonlocal-changes)
|
||||
file))
|
||||
|
||||
(rx/of
|
||||
(when (seq (:redo-changes local-changes))
|
||||
(dch/commit-changes (assoc local-changes
|
||||
:file-id (:id local-file))))
|
||||
(when (seq (:redo-changes nonlocal-changes))
|
||||
(dch/commit-changes (assoc nonlocal-changes
|
||||
:file-id file-id))))))))))
|
||||
(rx/of
|
||||
(when (seq (:redo-changes local-changes))
|
||||
(dch/commit-changes (assoc local-changes
|
||||
:file-id (:id local-file))))
|
||||
(when (seq (:redo-changes nonlocal-changes))
|
||||
(dch/commit-changes (assoc nonlocal-changes
|
||||
:file-id file-id)))))))))))
|
||||
|
||||
(defn update-component-sync
|
||||
[shape-id file-id]
|
||||
(ptk/reify ::update-component-sync
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [current-file-id (:current-file-id state)
|
||||
page (wsh/lookup-page state)
|
||||
shape (ctn/get-shape page shape-id)
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(update-component shape-id)
|
||||
(sync-file current-file-id file-id :components (:component-id shape))
|
||||
(when (not= current-file-id file-id)
|
||||
(sync-file file-id file-id :components (:component-id shape)))
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
([shape-id file-id] (update-component-sync shape-id file-id nil))
|
||||
([shape-id file-id undo-group]
|
||||
(ptk/reify ::update-component-sync
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [current-file-id (:current-file-id state)
|
||||
page (wsh/lookup-page state)
|
||||
shape (ctn/get-shape page shape-id)
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(update-component shape-id undo-group)
|
||||
(sync-file current-file-id file-id :components (:component-id shape) undo-group)
|
||||
(when (not= current-file-id file-id)
|
||||
(sync-file file-id file-id :components (:component-id shape) undo-group))
|
||||
(dwu/commit-undo-transaction undo-id)))))))
|
||||
|
||||
(defn update-component-in-bulk
|
||||
[shapes file-id]
|
||||
|
@ -666,7 +669,7 @@
|
|||
(let [undo-id (js/Symbol)]
|
||||
(rx/concat
|
||||
(rx/of (dwu/start-undo-transaction undo-id))
|
||||
(rx/map #(update-component-sync (:id %) file-id) (rx/from shapes))
|
||||
(rx/map #(update-component-sync (:id %) file-id (uuid/next)) (rx/from shapes))
|
||||
(rx/of (dwu/commit-undo-transaction undo-id)))))))
|
||||
|
||||
(declare sync-file-2nd-stage)
|
||||
|
@ -683,6 +686,8 @@
|
|||
([file-id library-id]
|
||||
(sync-file file-id library-id nil nil))
|
||||
([file-id library-id asset-type asset-id]
|
||||
(sync-file file-id library-id asset-type asset-id nil))
|
||||
([file-id library-id asset-type asset-id undo-group]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(us/assert ::us/uuid library-id)
|
||||
(us/assert (s/nilable #{:colors :components :typographies}) asset-type)
|
||||
|
@ -702,7 +707,8 @@
|
|||
:file (dwlh/pretty-file file-id state)
|
||||
:library (dwlh/pretty-file library-id state)
|
||||
:asset-type asset-type
|
||||
:asset-id asset-id)
|
||||
:asset-id asset-id
|
||||
:undo-group undo-group)
|
||||
(let [file (wsh/get-file state file-id)
|
||||
|
||||
sync-components? (or (nil? asset-type) (= asset-type :components))
|
||||
|
@ -711,7 +717,8 @@
|
|||
|
||||
library-changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/set-undo-group undo-group))
|
||||
[(when sync-components?
|
||||
(dwlh/generate-sync-library it file-id :components asset-id library-id state))
|
||||
(when sync-colors?
|
||||
|
@ -720,7 +727,8 @@
|
|||
(dwlh/generate-sync-library it file-id :typographies asset-id library-id state))])
|
||||
file-changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/set-undo-group undo-group))
|
||||
[(when sync-components?
|
||||
(dwlh/generate-sync-file it file-id :components asset-id library-id state))
|
||||
(when sync-colors?
|
||||
|
@ -751,7 +759,7 @@
|
|||
:library-id library-id})))
|
||||
(when (and (seq (:redo-changes library-changes))
|
||||
sync-components?)
|
||||
(rx/of (sync-file-2nd-stage file-id library-id asset-id))))))))))
|
||||
(rx/of (sync-file-2nd-stage file-id library-id asset-id undo-group))))))))))
|
||||
|
||||
(defn- sync-file-2nd-stage
|
||||
"If some components have been modified, we need to launch another synchronization
|
||||
|
@ -762,7 +770,7 @@
|
|||
;; recursively. But for this not to cause an infinite loop, we need to
|
||||
;; implement updated-at at component level, to detect what components have
|
||||
;; not changed, and then not to apply sync and terminate the loop.
|
||||
[file-id library-id asset-id]
|
||||
[file-id library-id asset-id undo-group]
|
||||
(us/assert ::us/uuid file-id)
|
||||
(us/assert ::us/uuid library-id)
|
||||
(us/assert (s/nilable ::us/uuid) asset-id)
|
||||
|
@ -775,7 +783,8 @@
|
|||
(let [file (wsh/get-file state file-id)
|
||||
changes (reduce
|
||||
pcb/concat-changes
|
||||
(pcb/empty-changes it)
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/set-undo-group undo-group))
|
||||
[(dwlh/generate-sync-file it file-id :components asset-id library-id state)
|
||||
(dwlh/generate-sync-library it file-id :components asset-id library-id state)])]
|
||||
|
||||
|
@ -849,7 +858,7 @@
|
|||
|
||||
check-changes
|
||||
(fn [[event data]]
|
||||
(let [{:keys [changes save-undo?]} (deref event)
|
||||
(let [{:keys [changes save-undo? undo-group]} (deref event)
|
||||
components-changed (reduce #(into %1 (ch/components-changed data %2))
|
||||
#{}
|
||||
changes)]
|
||||
|
@ -857,7 +866,7 @@
|
|||
(log/info :msg "DETECTED COMPONENTS CHANGED"
|
||||
:ids (map str components-changed))
|
||||
(run! st/emit!
|
||||
(map #(update-component-sync % (:id data))
|
||||
(map #(update-component-sync % (:id data) undo-group)
|
||||
components-changed)))))]
|
||||
|
||||
(when components-v2
|
||||
|
|
|
@ -548,7 +548,7 @@
|
|||
(defn duplicate-selected
|
||||
([move-delta?]
|
||||
(duplicate-selected move-delta? false))
|
||||
([move-delta? add-group-id?]
|
||||
([move-delta? add-undo-group?]
|
||||
(ptk/reify ::duplicate-selected
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
|
@ -567,7 +567,7 @@
|
|||
changes (->> (prepare-duplicate-changes objects page selected delta it libraries)
|
||||
(duplicate-changes-update-indices objects selected))
|
||||
|
||||
changes (cond-> changes add-group-id? (assoc :group-id (uuid/random)))
|
||||
changes (cond-> changes add-undo-group? (assoc :undo-group (uuid/random)))
|
||||
|
||||
id-original (first selected)
|
||||
|
||||
|
|
|
@ -67,11 +67,11 @@
|
|||
(add-undo-entry state entry))))
|
||||
|
||||
(defn- accumulate-undo-entry
|
||||
[state {:keys [undo-changes redo-changes group-id]}]
|
||||
[state {:keys [undo-changes redo-changes undo-group]}]
|
||||
(-> state
|
||||
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
|
||||
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))
|
||||
(assoc-in [:workspace-undo :transaction :group-id] group-id)))
|
||||
(assoc-in [:workspace-undo :transaction :undo-group] undo-group)))
|
||||
|
||||
(defn append-undo
|
||||
[entry stack?]
|
||||
|
@ -79,29 +79,31 @@
|
|||
(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)
|
||||
(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 []})
|
||||
|
||||
(defn start-undo-transaction [id]
|
||||
(defn start-undo-transaction
|
||||
"Start a transaction, so that every changes inside are added together in a single undo entry."
|
||||
[id]
|
||||
(ptk/reify ::start-undo-transaction
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; We commit the old transaction before starting the new one
|
||||
(let [current-tx (get-in state [:workspace-undo :transaction])
|
||||
pending-tx (get-in state [:workspace-undo :transactions-pending])]
|
||||
(let [current-tx (get-in state [:workspace-undo :transaction])
|
||||
pending-tx (get-in state [:workspace-undo :transactions-pending])]
|
||||
(cond-> state
|
||||
(nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx)
|
||||
(nil? pending-tx) (assoc-in [:workspace-undo :transactions-pending] #{id})
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.component
|
||||
(:require
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
|
|
Loading…
Add table
Reference in a new issue