diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 8a0e5430b..3fa2aeeb0 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -31,11 +31,86 @@ (defn with-objects [changes objects] (vary-meta changes assoc ::objects objects)) +(defn amend-last-change + "Modify the last redo-changes added with an update function." + [changes f] + (update changes :redo-changes + #(conj (pop %) (f (peek %))))) + +(defn amend-changes + "Modify all redo-changes with an update function." + [changes f] + (update changes :redo-changes #(mapv f %))) + +(defn- assert-page-id + [changes] + (assert (contains? (meta changes) ::page-id) "Give a page-id or call (with-page) before using this function")) + +(defn- assert-page + [changes] + (assert (contains? (meta changes) ::page) "Call (with-page) before using this function")) + +(defn- assert-objects + [changes] + (assert (contains? (meta changes) ::objects) "Call (with-objects) before using this function")) + +;; Page changes + +(defn add-empty-page + [changes id name] + (-> changes + (update :redo-changes conj {:type :add-page :id id :name name}) + (update :undo-changes conj {:type :del-page :id id}))) + +(defn add-page + [changes id page] + (-> changes + (update :redo-changes conj {:type :add-page :id id :page page}) + (update :undo-changes conj {:type :del-page :id id}))) + +(defn mod-page + [changes page new-name] + (-> changes + (update :redo-changes conj {:type :mod-page :id (:id page) :name new-name}) + (update :undo-changes conj {:type :mod-page :id (:id page) :name (:name page)}))) + +(defn del-page + [changes page] + (-> changes + (update :redo-changes conj {:type :del-page :id (:id page)}) + (update :undo-changes conj {:type :add-page :id (:id page) :page page}))) + +(defn move-page + [changes page-id index prev-index] + (-> changes + (update :redo-changes conj {:type :mov-page :id page-id :index index}) + (update :undo-changes conj {:type :mov-page :id page-id :index prev-index}))) + +(defn set-page-option + [changes option-key option-val] + (assert-page changes) + (let [page-id (::page-id (meta changes)) + page (::page (meta changes)) + old-val (get-in page [:options option-key])] + + (-> changes + (update :redo-changes conj {:type :set-option + :page-id page-id + :option option-key + :value option-val}) + (update :undo-changes conj {:type :set-option + :page-id page-id + :option option-key + :value old-val})))) + +;; Shape tree changes + (defn add-obj ([changes obj] (add-obj changes obj nil)) ([changes obj {:keys [index ignore-touched] :or {index ::undefined ignore-touched false}}] + (assert-page-id changes) (let [obj (cond-> obj (not= index ::undefined) (assoc :index index)) @@ -60,10 +135,12 @@ (update :undo-changes d/preconj del-change))))) (defn change-parent - ([changes parent-id shapes] (change-parent changes parent-id shapes nil)) - ([changes parent-id shapes index] - (assert (contains? (meta changes) ::objects) "Call (with-objects) first to use this function") + ([changes parent-id shapes] + (change-parent changes parent-id shapes nil)) + ([changes parent-id shapes index] + (assert-page-id changes) + (assert-objects changes) (let [objects (::objects (meta changes)) set-parent-change (cond-> {:type :mov-objects @@ -88,18 +165,6 @@ (update :redo-changes conj set-parent-change) (update :undo-changes #(reduce mk-undo-change % shapes)))))) -(defn- generate-operation - "Given an object old and new versions and an attribute will append into changes - the set and undo operations" - [changes attr old new ignore-geometry?] - (let [old-val (get old attr) - new-val (get new attr)] - (if (= old-val new-val) - changes - (-> changes - (update :rops conj {:type :set :attr attr :val new-val :ignore-geometry ignore-geometry?}) - (update :uops conj {:type :set :attr attr :val old-val :ignore-touched true}))))) - (defn update-shapes "Calculate the changes and undos to be done when a function is applied to a single object" @@ -107,9 +172,20 @@ (update-shapes changes ids update-fn nil)) ([changes ids update-fn {:keys [attrs ignore-geometry?] :or {attrs nil ignore-geometry? false}}] - (assert (contains? (meta changes) ::objects) "Call (with-objects) first to use this function") + (assert-page-id changes) + (assert-objects changes) (let [objects (::objects (meta changes)) + generate-operation + (fn [changes attr old new ignore-geometry?] + (let [old-val (get old attr) + new-val (get new attr)] + (if (= old-val new-val) + changes + (-> changes + (update :rops conj {:type :set :attr attr :val new-val :ignore-geometry ignore-geometry?}) + (update :uops conj {:type :set :attr attr :val old-val :ignore-touched true}))))) + update-shape (fn [changes id] (let [old-obj (get objects id) @@ -141,7 +217,8 @@ (defn remove-objects [changes ids] - (assert (contains? (meta changes) ::objects) "Call (with-objects) first to use this function") + (assert-page-id changes) + (assert-objects changes) (let [page-id (::page-id (meta changes)) objects (::objects (meta changes)) @@ -162,6 +239,7 @@ :parent-id (:frame-id shape) :frame-id (:frame-id shape) :id id + :index (cph/get-position-on-parent objects (:id shape)) :obj (cond-> shape (contains? shape :shapes) (assoc :shapes []))}))) @@ -184,44 +262,12 @@ (reduce add-undo-change-parent $ ids) (reduce add-undo-change-shape $ ids)))))) -(defn move-page - [chdata index prev-index] - (let [page-id (::page-id (meta chdata))] - (-> chdata - (update :redo-changes conj {:type :mov-page :id page-id :index index}) - (update :undo-changes conj {:type :mov-page :id page-id :index prev-index})))) - -(defn set-page-option - [chdata option-key option-val] - (let [page-id (::page-id (meta chdata)) - page (::page (meta chdata)) - old-val (get-in page [:options option-key])] - - (-> chdata - (update :redo-changes conj {:type :set-option - :page-id page-id - :option option-key - :value option-val}) - (update :undo-changes conj {:type :set-option - :page-id page-id - :option option-key - :value old-val})))) - -(defn reg-objects - [chdata shape-ids] - (let [page-id (::page-id (meta chdata))] - (-> chdata - (update :redo-changes conj {:type :reg-objects :page-id page-id :shapes shape-ids})))) - ;; No need to do anything to undo - -(defn amend-last-change - "Modify the last redo-changes added with an update function." - [chdata f] - (update chdata :redo-changes - #(conj (pop %) (f (peek %))))) - -(defn amend-changes - "Modify all redo-changes with an update function." - [chdata f] - (update chdata :redo-changes #(mapv f %))) +(defn resize-parents + [changes ids] + (assert-page-id changes) + (let [page-id (::page-id (meta changes)) + shapes (vec ids)] + (-> changes + (update :redo-changes conj {:type :reg-objects :page-id page-id :shapes shapes}) + (update :undo-changes conj {:type :reg-objects :page-id page-id :shapes shapes})))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 7ef8e680e..f88fb657d 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -320,14 +320,10 @@ unames (dwc/retrieve-used-names pages) name (dwc/generate-unique-name unames "Page-1") - rchange {:type :add-page - :id id - :name name} - uchange {:type :del-page - :id id}] - (rx/of (dch/commit-changes {:redo-changes [rchange] - :undo-changes [uchange] - :origin it}))))))) + changes (-> (pcb/empty-changes it) + (pcb/add-empty-page id name))] + + (rx/of (dch/commit-changes changes))))))) (defn duplicate-page [page-id] @@ -342,13 +338,10 @@ page (-> page (assoc :name name :id id)) - rchange {:type :add-page - :page page} - uchange {:type :del-page - :id id}] - (rx/of (dch/commit-changes {:redo-changes [rchange] - :undo-changes [uchange] - :origin it})))))) + changes (-> (pcb/empty-changes it) + (pcb/add-page id page))] + + (rx/of (dch/commit-changes changes)))))) (s/def ::rename-page (s/keys :req-un [::id ::name])) @@ -360,33 +353,26 @@ (ptk/reify ::rename-page ptk/WatchEvent (watch [it state _] - (let [page (get-in state [:workspace-data :pages-index id]) - rchg {:type :mod-page - :id id - :name name} - uchg {:type :mod-page - :id id - :name (:name page)}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it})))))) + (let [page (get-in state [:workspace-data :pages-index id]) + changes (-> (pcb/empty-changes it) + (pcb/mod-page page name))] + + (rx/of (dch/commit-changes changes)))))) (declare purge-page) (declare go-to-file) -;; TODO: for some reason, the page-id here in some circumstances is `nil` (defn delete-page [id] (ptk/reify ::delete-page ptk/WatchEvent (watch [it state _] (let [page (get-in state [:workspace-data :pages-index id]) - rchg {:type :del-page :id id} - uchg {:type :add-page :page page}] - (rx/of (dch/commit-changes {:redo-changes [rchg] - :undo-changes [uchg] - :origin it}) + changes (-> (pcb/empty-changes it) + (pcb/del-page page))] + + (rx/of (dch/commit-changes changes) (when (= id (:current-page-id state)) go-to-file)))))) @@ -773,249 +759,93 @@ (ptk/reify ::vertical-order-selected ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - selected (wsh/lookup-selected state) - rchanges (mapv (fn [id] - (let [obj (get objects id) - parent (get objects (:parent-id obj)) - shapes (:shapes parent) - cindex (d/index-of shapes id) - nindex (case loc - :top (count shapes) - :down (max 0 (- cindex 1)) - :up (min (count shapes) (+ (inc cindex) 1)) - :bottom 0)] - {:type :mov-objects - :parent-id (:parent-id obj) - :frame-id (:frame-id obj) - :page-id page-id - :index nindex - :shapes [id]})) - selected) + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + selected-shapes (->> (wsh/lookup-selected state) + (map (d/getf objects))) - uchanges (mapv (fn [id] - (let [obj (get objects id)] - {:type :mov-objects - :parent-id (:parent-id obj) - :frame-id (:frame-id obj) - :page-id page-id - :shapes [id] - :index (cph/get-position-on-parent objects id)})) - selected)] - ;; TODO: maybe missing the :reg-objects event? - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it})))))) + move-shape + (fn [changes shape] + (let [parent (get objects (:parent-id shape)) + sibling-ids (:shapes parent) + current-index (d/index-of sibling-ids (:id shape)) + new-index (case loc + :top (count sibling-ids) + :down (max 0 (- current-index 1)) + :up (min (count sibling-ids) (+ (inc current-index) 1)) + :bottom 0)] + (pcb/change-parent changes + (:id parent) + [shape] + new-index))) + + changes (reduce move-shape + (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects)) + selected-shapes)] + + (rx/of (dch/commit-changes changes)))))) ;; --- Change Shape Order (D&D Ordering) -(defn relocate-shapes-changes [objects parents parent-id page-id to-index ids +(defn relocate-shapes-changes [it objects parents parent-id page-id to-index ids groups-to-delete groups-to-unmask shapes-to-detach shapes-to-reroot shapes-to-deroot shapes-to-unconstraint] - (let [;; Changes to the shapes that are being move - r-mov-change - [{:type :mov-objects - :parent-id parent-id - :page-id page-id - :index to-index - :shapes (vec (reverse ids))}] + (let [shapes (map (d/getf objects) ids)] - u-mov-change - (map (fn [id] - (let [obj (get objects id)] - {:type :mov-objects - :parent-id (:parent-id obj) - :page-id page-id - :index (cph/get-position-on-parent objects id) - :shapes [id]})) - (reverse ids)) + (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects) - ;; Changes deleting empty groups - r-del-change - (map (fn [group-id] - {:type :del-obj - :page-id page-id - :id group-id}) - groups-to-delete) + ; Move the shapes + (pcb/change-parent parent-id + (reverse shapes) + to-index) - u-del-change - (concat - ;; Create the groups - (map (fn [group-id] - (let [group (get objects group-id)] - {:type :add-obj - :page-id page-id - :parent-id parent-id - :frame-id (:frame-id group) - :id group-id - :obj (-> group - (assoc :shapes []))})) - groups-to-delete) - ;; Creates the hierarchy - (map (fn [group-id] - (let [group (get objects group-id)] - {:type :mov-objects - :page-id page-id - :parent-id (:id group) - :shapes (:shapes group)})) - groups-to-delete)) + ; Remove empty groups + (pcb/remove-objects groups-to-delete) - ;; Changes removing the masks from the groups without mask shape - r-mask-change - (map (fn [group-id] - {:type :mod-obj - :page-id page-id - :id group-id - :operations [{:type :set - :attr :masked-group? - :val false}]}) - groups-to-unmask) + ; Unmask groups whose mask have moved outside + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group? false))) - u-mask-change - (map (fn [group-id] - (let [group (get objects group-id)] - {:type :mod-obj - :page-id page-id - :id group-id - :operations [{:type :set - :attr :masked-group? - :val (:masked-group? group)}]})) - groups-to-unmask) + ; Detach shapes moved out of their component + (pcb/update-shapes shapes-to-detach + (fn [shape] + (assoc shape :component-id nil + :component-file nil + :component-root? nil + :remote-synced? nil + :shape-ref nil + :touched nil))) - ;; Changes to the components metadata + ; Make non root a component moved inside another one + (pcb/update-shapes shapes-to-deroot + (fn [shape] + (assoc shape :component-root? nil))) - detach-keys [:component-id :component-file :component-root? :remote-synced? :shape-ref :touched] + ; Make root a subcomponent moved outside its parent component + (pcb/update-shapes shapes-to-reroot + (fn [shape] + (assoc shape :component-root? true))) - r-detach-change - (map (fn [id] - {:type :mod-obj - :page-id page-id - :id id - :operations (mapv #(hash-map :type :set :attr % :val nil) detach-keys)}) - shapes-to-detach) + ; Reset constraints depending on the new parent + (pcb/update-shapes shapes-to-unconstraint + (fn [shape] + (let [parent (get objects parent-id) + frame-id (if (= (:type parent) :frame) + (:id parent) + (:frame-id parent)) + moved-shape (assoc shape + :parent-id parent-id + :frame-id frame-id)] + (assoc shape :constraints-h (gsh/default-constraints-h moved-shape) + :constraints-v (gsh/default-constraints-v moved-shape)))) + {:ignore-touched true}) - u-detach-change - (map (fn [id] - (let [obj (get objects id)] - {:type :mod-obj - :page-id page-id - :id id - :operations (mapv #(hash-map :type :set :attr % :val (get obj %)) detach-keys)})) - shapes-to-detach) - - r-deroot-change - (map (fn [id] - {:type :mod-obj - :page-id page-id - :id id - :operations [{:type :set - :attr :component-root? - :val nil}]}) - shapes-to-deroot) - - u-deroot-change - (map (fn [id] - {:type :mod-obj - :page-id page-id - :id id - :operations [{:type :set - :attr :component-root? - :val true}]}) - shapes-to-deroot) - - r-reroot-change - (map (fn [id] - {:type :mod-obj - :page-id page-id - :id id - :operations [{:type :set - :attr :component-root? - :val true}]}) - shapes-to-reroot) - - u-reroot-change - (map (fn [id] - {:type :mod-obj - :page-id page-id - :id id - :operations [{:type :set - :attr :component-root? - :val nil}]}) - shapes-to-reroot) - - - ;; Changes resetting constraints - - r-unconstraint-change - (map (fn [id] - (let [obj (get objects id) - parent (get objects parent-id) - frame-id (if (= (:type parent) :frame) - (:id parent) - (:frame-id parent))] - {:type :mod-obj - :page-id page-id - :id id - :operations [{:type :set - :attr :constraints-h - :val (gsh/default-constraints-h - (assoc obj :parent-id parent-id :frame-id frame-id)) - :ignore-touched true} - {:type :set - :attr :constraints-v - :val (gsh/default-constraints-v - (assoc obj :parent-id parent-id :frame-id frame-id)) - :ignore-touched true}]})) - shapes-to-unconstraint) - - u-unconstraint-change - (map (fn [id] - (let [obj (get objects id)] - {:type :mod-obj - :page-id page-id - :id id - :operations [{:type :set - :attr :constraints-h - :val (:constraints-h obj) - :ignore-touched true} - {:type :set - :attr :constraints-v - :val (:constraints-v obj) - :ignore-touched true}]})) - shapes-to-unconstraint) - - r-reg-change - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}] - - u-reg-change - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}] - - rchanges (d/concat-vec - r-mov-change - r-del-change - r-mask-change - r-detach-change - r-deroot-change - r-reroot-change - r-unconstraint-change - r-reg-change) - - uchanges (d/concat-vec - u-del-change - u-reroot-change - u-deroot-change - u-detach-change - u-mask-change - u-mov-change - u-unconstraint-change - u-reg-change)] - [rchanges uchanges])) + ; Resize parent containers that need to + (pcb/resize-parents parents)))) (defn relocate-shapes [ids parent-id to-index] @@ -1114,23 +944,21 @@ [[] [] []] ids) - [rchanges uchanges] - (relocate-shapes-changes objects - parents - parent-id - page-id - to-index - ids - groups-to-delete - groups-to-unmask - shapes-to-detach - shapes-to-reroot - shapes-to-deroot - ids)] + changes (relocate-shapes-changes it + objects + parents + parent-id + page-id + to-index + ids + groups-to-delete + groups-to-unmask + shapes-to-detach + shapes-to-reroot + shapes-to-deroot + ids)] - (rx/of (dch/commit-changes {:redo-changes rchanges - :undo-changes uchanges - :origin it}) + (rx/of (dch/commit-changes changes) (dwc/expand-collapse parent-id)))))) (defn relocate-selected-shapes @@ -1177,8 +1005,8 @@ (watch [it state _] (let [prev-index (-> (get-in state [:workspace-data :pages]) (d/index-of id)) - changes (-> (pcb/empty-changes it id) - (pcb/move-page index prev-index))] + changes (-> (pcb/empty-changes it) + (pcb/move-page id index prev-index))] (rx/of (dch/commit-changes changes)))))) ;; --- Shape / Selection Alignment and Distribution @@ -1851,7 +1679,7 @@ ;; Adds a reg-objects operation so the groups are updated. We add all the new objects new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id)) - changes (pcb/reg-objects changes new-objects-ids) + changes (pcb/resize-parents changes new-objects-ids) selected (->> changes :redo-changes @@ -1962,19 +1790,12 @@ (ptk/reify ::change-canvas-color ptk/WatchEvent (watch [it state _] - (let [page-id (get state :current-page-id) - options (wsh/lookup-page-options state page-id) - previous-color (:background options)] - (rx/of (dch/commit-changes - {:redo-changes [{:type :set-option - :page-id page-id - :option :background - :value (:color color)}] - :undo-changes [{:type :set-option - :page-id page-id - :option :background - :value previous-color}] - :origin it})))))) + (let [page (wsh/lookup-page state) + changes (-> (pcb/empty-changes it) + (pcb/with-page page) + (pcb/set-page-option :background (:color color)))] + + (rx/of (dch/commit-changes changes)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Artboard