diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 7f83d54a9..bbc535f57 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -749,7 +749,6 @@ (d/update-in-when data [:pages-index page-id :objects] reg-objects))) - (defmethod process-change :mov-objects [data {:keys [parent-id shapes index page-id] :as change}] (letfn [(is-valid-move? [objects shape-id] @@ -761,34 +760,40 @@ (let [prev-shapes (or prev-shapes [])] (if index (cph/insert-at-index prev-shapes index shapes) - (reduce (fn [acc id] - (if (some #{id} acc) - acc - (conj acc id))) - prev-shapes - shapes)))) + (cph/append-at-the-end prev-shapes shapes)))) + + (check-insert-items [prev-shapes parent index shapes] + (if-not (:masked-group? parent) + (insert-items prev-shapes index shapes) + ;; For masked groups, the first shape is the mask + ;; and it cannot be moved. + (let [mask-id (first prev-shapes) + other-ids (rest prev-shapes) + not-mask-shapes (strip-id shapes mask-id) + new-index (if (nil? index) nil (max (dec index) 0)) + new-shapes (insert-items other-ids new-index not-mask-shapes)] + (d/concat [mask-id] new-shapes)))) (strip-id [coll id] (filterv #(not= % id) coll)) - (remove-from-old-parent [cpindex objects shape-id] - (let [prev-parent-id (get cpindex shape-id)] - ;; Do nothing if the parent id of the shape is the same as - ;; the new destination target parent id. - (if (= prev-parent-id parent-id) - objects - (loop [sid shape-id - pid prev-parent-id - objects objects] - (let [obj (get objects pid)] - (if (and (= 1 (count (:shapes obj))) - (= sid (first (:shapes obj))) - (= :group (:type obj))) - (recur pid - (:parent-id obj) - (dissoc objects pid)) - (update-in objects [pid :shapes] strip-id sid))))))) - + (remove-from-old-parent [cpindex objects shape-id] + (let [prev-parent-id (get cpindex shape-id)] + ;; Do nothing if the parent id of the shape is the same as + ;; the new destination target parent id. + (if (= prev-parent-id parent-id) + objects + (loop [sid shape-id + pid prev-parent-id + objects objects] + (let [obj (get objects pid)] + (if (and (= 1 (count (:shapes obj))) + (= sid (first (:shapes obj))) + (= :group (:type obj))) + (recur pid + (:parent-id obj) + (dissoc objects pid)) + (update-in objects [pid :shapes] strip-id sid))))))) (update-parent-id [objects id] (update objects id assoc :parent-id parent-id)) @@ -820,7 +825,7 @@ (if valid? (as-> objects $ - (update-in $ [parent-id :shapes] insert-items index shapes) + (update-in $ [parent-id :shapes] check-insert-items parent index shapes) (reduce update-parent-id $ shapes) (reduce (partial remove-from-old-parent cpindex) $ shapes) (reduce (partial update-frame-ids frm-id) $ (get-in $ [parent-id :shapes]))) diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index 1432923bd..b8fcef2f5 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -116,6 +116,15 @@ ids (remove p? after)))) +(defn append-at-the-end + [prev-ids ids] + (reduce (fn [acc id] + (if (some #{id} acc) + acc + (conj acc id))) + prev-ids + ids)) + (defn select-toplevel-shapes ([objects] (select-toplevel-shapes objects nil)) ([objects {:keys [include-frames?] :or {include-frames? false}}] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index ba02229f5..b30886572 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -677,10 +677,8 @@ (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) - del-change #(array-map :type :del-obj :page-id page-id :id %) - get-empty-parents - (fn get-empty-parents [parents] + (fn [parents] (->> parents (map (fn [id] (let [obj (get objects id)] @@ -690,43 +688,78 @@ (take-while (complement nil?)) (map :id))) - rchanges - (reduce (fn [res id] - (let [children (cph/get-children id objects) - parents (cph/get-parents id objects)] - (d/concat res - (map del-change (reverse children)) - [(del-change id)] - (map del-change (get-empty-parents parents)) - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}]))) - [] + groups-to-unmask + (reduce (fn [group-ids id] + ;; When the shape to delete is the mask of a masked group, + ;; the mask condition must be removed, and it must be + ;; converted to a normal group. + (let [obj (get objects id) + parent (get objects (:parent-id obj))] + (if (and (:masked-group? parent) + (= id (first (:shapes parent)))) + (conj group-ids (:id parent)) + group-ids))) + #{} ids) + + rchanges + (d/concat + (reduce (fn [res id] + (let [children (cph/get-children id objects) + parents (cph/get-parents id objects) + del-change #(array-map + :type :del-obj + :page-id page-id + :id %)] + (d/concat res + (map del-change (reverse children)) + [(del-change id)] + (map del-change (get-empty-parents parents)) + [{:type :reg-objects + :page-id page-id + :shapes (vec parents)}]))) + [] + ids) + (map #(array-map + :type :mod-obj + :page-id page-id + :id % + :operations [{:type :set + :attr :masked-group? + :val false}]) + groups-to-unmask)) uchanges - (reduce (fn [res id] - (let [children (cph/get-children id objects) - parents (cph/get-parents id objects) - add-chg (fn [id] - (let [item (get objects id)] - {:type :add-obj - :id (:id item) - :page-id page-id - :index (cph/position-on-parent id objects) - :frame-id (:frame-id item) - :parent-id (:parent-id item) - :obj item}))] - (d/concat res - (map add-chg (reverse (get-empty-parents parents))) - [(add-chg id)] - (map add-chg children) - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}]))) - [] - ids) - ] + (d/concat + (reduce (fn [res id] + (let [children (cph/get-children id objects) + parents (cph/get-parents id objects) + add-change (fn [id] + (let [item (get objects id)] + {:type :add-obj + :id (:id item) + :page-id page-id + :index (cph/position-on-parent id objects) + :frame-id (:frame-id item) + :parent-id (:parent-id item) + :obj item}))] + (d/concat res + (map add-change (reverse (get-empty-parents parents))) + [(add-change id)] + (map add-change children) + [{:type :reg-objects + :page-id page-id + :shapes (vec parents)}]))) + [] + ids) + (map #(array-map + :type :mod-obj + :page-id page-id + :id % + :operations [{:type :set + :attr :masked-group? + :val true}]) + groups-to-unmask))] ;; (println "================ rchanges") ;; (cljs.pprint/pprint rchanges) @@ -808,29 +841,60 @@ (conj res (cph/get-parent (first ids) objects)) (next ids)))) - rchanges [{:type :mov-objects - :parent-id parent-id - :page-id page-id - :index to-index - :shapes (vec (reverse ids))} - {:type :reg-objects - :page-id page-id - :shapes parents}] + groups-to-unmask + (reduce (fn [group-ids id] + ;; When a masked group loses its mask shape, because it's + ;; moved outside the group, the mask condition must be + ;; removed, and it must be converted to a normal group. + (let [obj (get objects id) + parent (get objects (:parent-id obj))] + (if (and (:masked-group? parent) + (= id (first (:shapes parent))) + (not= (:id parent) parent-id)) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) - uchanges - (reduce (fn [res id] - (let [obj (get objects id)] - (conj res - {:type :mov-objects - :parent-id (:parent-id obj) + rchanges (d/concat + [{:type :mov-objects + :parent-id parent-id + :page-id page-id + :index to-index + :shapes (vec (reverse ids))} + {:type :reg-objects + :page-id page-id + :shapes parents}] + (map (fn [group-id] + {:type :mod-obj :page-id page-id - :index (cph/position-on-parent id objects) - :shapes [id]}))) - [] (reverse ids)) - uchanges (conj uchanges - {:type :reg-objects + :id group-id + :operations [{:type :set + :attr :masked-group? + :val false}]}) + groups-to-unmask)) + + uchanges (d/concat + (reduce (fn [res id] + (let [obj (get objects id)] + (conj res + {:type :mov-objects + :parent-id (:parent-id obj) + :page-id page-id + :index (cph/position-on-parent id objects) + :shapes [id]}))) + [] (reverse ids)) + [{:type :reg-objects :page-id page-id - :shapes parents})] + :shapes parents}] + (map (fn [group-id] + {:type :mod-obj + :page-id page-id + :id group-id + :operations [{:type :set + :attr :masked-group? + :val true}]}) + groups-to-unmask))] ;; (println "================ rchanges") ;; (cljs.pprint/pprint rchanges) @@ -1577,9 +1641,10 @@ "+" #(st/emit! (increase-zoom nil)) "-" #(st/emit! (decrease-zoom nil)) "ctrl+g" #(st/emit! group-selected) - "ctrl+shift+m" #(st/emit! mask-group) - "ctrl+k" #(st/emit! dwl/add-component) "shift+g" #(st/emit! ungroup-selected) + "ctrl+m" #(st/emit! mask-group) + "shift+m" #(st/emit! unmask-group) + "ctrl+k" #(st/emit! dwl/add-component) "shift+0" #(st/emit! reset-zoom) "shift+1" #(st/emit! zoom-to-fit-all) "shift+2" #(st/emit! zoom-to-selected-shape) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 697fc0273..c774fd04a 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -105,7 +105,7 @@ :shortcut "Ctrl + g" :on-click do-create-group}] [:& menu-entry {:title "Mask" - :shortcut "Ctrl + Shift + M" + :shortcut "Ctrl + M" :on-click do-mask-group}]]) (when (and (= (count selected) 1) (= (:type shape) :group)) @@ -115,10 +115,10 @@ :on-click do-remove-group}] (if (:masked-group? shape) [:& menu-entry {:title "Unmask" - :shortcut "Ctrl + Shift + M" + :shortcut "Shift + M" :on-click do-unmask-group}] [:& menu-entry {:title "Mask" - :shortcut "Ctrl + Shift + M" + :shortcut "Ctrl + M" :on-click do-mask-group}])]) (if (:hidden shape)