diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index 625b201d9..8c195e7f7 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -1820,10 +1820,53 @@ (pcb/with-objects (:objects container)) (generate-detach-instance container libraries id)))) +(defn generate-update-shape-flags + [changes ids objects {:keys [blocked hidden] :as flags}] + (let [update-fn + (fn [obj] + (cond-> obj + (boolean? blocked) (assoc :blocked blocked) + (boolean? hidden) (assoc :hidden hidden))) + + ids (if (boolean? blocked) + (into ids (->> ids (mapcat #(cfh/get-children-ids objects %)))) + ids)] + (-> changes + (pcb/update-shapes ids update-fn {:attrs #{:blocked :hidden}})))) + (defn generate-delete-shapes - [changes file page objects ids {:keys [components-v2 ignore-touched undo-group]}] - (let [changes (-> changes - (pcb/set-undo-group undo-group) + [changes file page objects ids {:keys [components-v2 ignore-touched component-swap]}] + (let [ids (cfh/clean-loops objects ids) + + in-component-copy? + (fn [shape-id] + ;; Look for shapes that are inside a component copy, but are + ;; not the root. In this case, they must not be deleted, + ;; but hidden (to be able to recover them more easily). + ;; Unless we are doing a component swap, in which case we want + ;; to delete the old shape + (let [shape (get objects shape-id)] + (and (ctn/has-any-copy-parent? objects shape) + (not component-swap)))) + + [ids-to-delete ids-to-hide] + (if components-v2 + (loop [ids-seq (seq ids) + ids-to-delete [] + ids-to-hide []] + (let [id (first ids-seq)] + (if (nil? id) + [ids-to-delete ids-to-hide] + (if (in-component-copy? id) + (recur (rest ids-seq) + ids-to-delete + (conj ids-to-hide id)) + (recur (rest ids-seq) + (conj ids-to-delete id) + ids-to-hide))))) + [ids []]) + + changes (-> changes (pcb/with-page page) (pcb/with-objects objects) (pcb/with-library-data file)) @@ -1840,7 +1883,7 @@ (conj group-ids (:id parent)) group-ids))) #{} - ids) + ids-to-delete) interacting-shapes (filter (fn [shape] @@ -1848,11 +1891,11 @@ ;; some interaction, this must be deleted, too. (let [interactions (:interactions shape)] (some #(and (ctsi/has-destination %) - (contains? ids (:destination %))) + (contains? ids-to-delete (:destination %))) interactions))) (vals objects)) - ids-set (set ids) + ids-set (set ids-to-delete) guides-to-remove (->> (dm/get-in page [:options :guides]) (vals) @@ -1867,7 +1910,7 @@ (filter (fn [flow] ;; If any of the deleted is a frame that starts a flow, ;; this must be deleted, too. - (contains? ids (:starting-frame flow))) + (contains? ids-to-delete (:starting-frame flow))) (-> page :options :flows)) all-parents @@ -1875,10 +1918,10 @@ ;; All parents of any deleted shape must be resized. (into res (cfh/get-parent-ids objects id))) (d/ordered-set) - ids) + ids-to-delete) all-children - (->> ids ;; Children of deleted shapes must be also deleted. + (->> ids-to-delete ;; Children of deleted shapes must be also deleted. (reduce (fn [res id] (into res (cfh/get-children-ids objects id))) []) @@ -1887,7 +1930,7 @@ find-all-empty-parents (fn recursive-find-empty-parents [empty-parents] - (let [all-ids (into empty-parents ids) + (let [all-ids (into empty-parents ids-to-delete) contains? (partial contains? all-ids) xform (comp (map lookup) (filter #(or (cfh/group-shape? %) (cfh/bool-shape? %))) @@ -1911,7 +1954,7 @@ (conj components (:component-id shape)) components))) [] - (into ids all-children)) + (into ids-to-delete all-children)) []) changes (-> changes @@ -1923,10 +1966,10 @@ (pcb/delete-component changes component-id (:id page))) changes components-to-delete) - changes (-> changes + (generate-update-shape-flags ids-to-hide objects {:hidden true}) (pcb/remove-objects all-children {:ignore-touched true}) - (pcb/remove-objects ids {:ignore-touched ignore-touched}) + (pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched}) (pcb/remove-objects empty-parents) (pcb/resize-parents all-parents) (pcb/update-shapes groups-to-unmask @@ -1938,10 +1981,67 @@ (fn [interactions] (into [] (remove #(and (ctsi/has-destination %) - (contains? ids (:destination %)))) + (contains? ids-to-delete (:destination %)))) interactions))))) (cond-> (seq starting-flows) (pcb/update-page-option :flows (fn [flows] (->> (map :id starting-flows) (reduce ctp/remove-flow flows))))))] - [changes all-parents])) \ No newline at end of file + [all-parents changes])) + +(defn generate-new-shape-for-swap + [changes shape file page libraries id-new-component index target-cell keep-props-values] + (let [objects (:objects page) + position (gpt/point (:x shape) (:y shape)) + changes (-> changes + (pcb/with-objects objects)) + position (-> position (with-meta {:cell target-cell})) + parent (get objects (:parent-id shape)) + inside-comp? (ctn/in-any-component? objects parent) + + [new-shape changes] + (generate-instantiate-component changes + objects + (:id file) + id-new-component + position + page + libraries + nil + (:parent-id shape) + (:frame-id shape) + {:force-frame? true}) + + new-shape (cond-> new-shape + ;; if the shape isn't inside a main component, it shouldn't have a swap slot + (and (nil? (ctk/get-swap-slot new-shape)) + inside-comp?) + (update :touched cfh/set-touched-group (-> (ctf/find-swap-slot shape + page + {:id (:id file) + :data file} + libraries) + (ctk/build-swap-slot-group))))] + + [new-shape (-> changes + ;; Restore the properties + (pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values)) + + ;; We need to set the same index as the original shape + (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true + :ignore-touched true}) + (change-touched new-shape + shape + (ctn/make-container page :page) + {}))])) + +(defn generate-component-swap + [changes objects shape file page libraries id-new-component index target-cell keep-props-values] + (let [[all-parents changes] + (-> changes + (generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:components-v2 true + :component-swap true})) + [new-shape changes] + (-> changes + (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] + [new-shape all-parents changes])) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 105d0a213..c3981cf1d 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -23,6 +23,7 @@ [app.common.types.shape.layout :as ctl] [app.common.types.typography :as ctt] [app.common.uuid :as uuid] + [app.main.data.comments :as dc] [app.main.data.events :as ev] [app.main.data.messages :as msg] [app.main.data.modal :as modal] @@ -31,7 +32,6 @@ [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.notifications :as-alias dwn] [app.main.data.workspace.selection :as dws] - [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.specialized-panel :as dwsp] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] @@ -465,12 +465,29 @@ (watch [it state _] (let [data (get state :workspace-data)] (if (features/active-feature? state "components/v2") - (let [component (ctkl/get-component data id) - page-id (:main-instance-page component) - root-id (:main-instance-id component)] + (let [component (ctkl/get-component data id) + page-id (:main-instance-page component) + root-id (:main-instance-id component) + file-id (:current-file-id state) + file (wsh/get-file state file-id) + page (wsh/lookup-page state page-id) + objects (wsh/lookup-page-objects state page-id) + components-v2 (features/active-feature? state "components/v2") + undo-group (uuid/next) + undo-id (js/Symbol) + [all-parents changes] + (-> (pcb/empty-changes it page-id) + ;; Deleting main root triggers component delete + (cflh/generate-delete-shapes file page objects #{root-id} {:components-v2 components-v2 + :undo-group undo-group + :undo-id undo-id}))] (rx/of + (dwu/start-undo-transaction undo-id) (dwt/clear-thumbnail (:current-file-id state) page-id root-id "component") - (dwsh/delete-shapes page-id #{root-id}))) ;; Deleting main root triggers component delete + (dc/detach-comment-thread #{root-id}) + (dch/commit-changes changes) + (ptk/data-event :layout/update {:ids all-parents :undo-group undo-group}) + (dwu/commit-undo-transaction undo-id))) (let [page-id (:current-page-id state) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) @@ -880,62 +897,6 @@ second) 0))))) -(defn- add-component-for-swap - [shape file page libraries id-new-component index target-cell keep-props-values {:keys [undo-group]}] - (dm/assert! (uuid? id-new-component)) - (ptk/reify ::add-component-for-swap - ptk/WatchEvent - (watch [it _ _] - (let [objects (:objects page) - position (gpt/point (:x shape) (:y shape)) - changes (-> (pcb/empty-changes it (:id page)) - (pcb/set-undo-group undo-group) - (pcb/with-objects objects)) - position (-> position (with-meta {:cell target-cell})) - parent (get objects (:parent-id shape)) - inside-comp? (ctn/in-any-component? objects parent) - - [new-shape changes] - (cflh/generate-instantiate-component changes - objects - (:id file) - id-new-component - position - page - libraries - nil - (:parent-id shape) - (:frame-id shape) - {:force-frame? true}) - - new-shape (cond-> new-shape - ; if the shape isn't inside a main component, it shouldn't have a swap slot - (and (nil? (ctk/get-swap-slot new-shape)) - inside-comp?) - (update :touched cfh/set-touched-group (-> (ctf/find-swap-slot shape - page - {:id (:id file) - :data file} - libraries) - (ctk/build-swap-slot-group)))) - - changes - (-> changes - ;; Restore the properties - (pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values)) - - ;; We need to set the same index as the original shape - (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true - :ignore-touched true}) - (cflh/change-touched new-shape - shape - (ctn/make-container page :page) - {}))] - - ;; First delete so we don't break the grid layout cells - (rx/of (dch/commit-changes changes) - (dws/select-shape (:id new-shape) true)))))) - (defn- component-swap "Swaps a component with another one" [shape file-id id-new-component] @@ -943,7 +904,7 @@ (dm/assert! (uuid? file-id)) (ptk/reify ::component-swap ptk/WatchEvent - (watch [_ state _] + (watch [it state _] ;; First delete shapes so we have space in the layout otherwise we can have problems ;; in the grid creating new rows/columns to make space (let [file (wsh/get-file state file-id) @@ -962,15 +923,18 @@ keep-props-values (select-keys shape ctk/swap-keep-attrs) undo-id (js/Symbol) - undo-group (uuid/next)] + undo-group (uuid/next) + + [new-shape all-parents changes] + (-> (pcb/empty-changes it (:id page)) + (pcb/set-undo-group undo-group) + (cflh/generate-component-swap objects shape file page libraries id-new-component index target-cell keep-props-values))] + (rx/of (dwu/start-undo-transaction undo-id) - (dwsh/delete-shapes nil (d/ordered-set (:id shape)) {:component-swap true - :undo-id undo-id - :undo-group undo-group}) - (add-component-for-swap shape file page libraries id-new-component index target-cell keep-props-values - {:undo-group undo-group}) - (ptk/data-event :layout/update {:ids [(:parent-id shape)] :undo-group undo-group}) + (dch/commit-changes changes) + (dws/select-shape (:id new-shape) true) + (ptk/data-event :layout/update {:ids all-parents :undo-group undo-group}) (dwu/commit-undo-transaction undo-id)))))) (defn component-multi-swap diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index b60195259..692ff9317 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -102,47 +102,15 @@ file (wsh/get-file state file-id) page (wsh/lookup-page state page-id) objects (wsh/lookup-page-objects state page-id) - components-v2 (features/active-feature? state "components/v2") - - ids (cfh/clean-loops objects ids) - - in-component-copy? - (fn [shape-id] - ;; Look for shapes that are inside a component copy, but are - ;; not the root. In this case, they must not be deleted, - ;; but hidden (to be able to recover them more easily). - ;; Unless we are doing a component swap, in which case we want - ;; to delete the old shape - (let [shape (get objects shape-id)] - (and (ctn/has-any-copy-parent? objects shape) - (not (:component-swap options))))) - - [ids-to-delete ids-to-hide] - (if components-v2 - (loop [ids-seq (seq ids) - ids-to-delete [] - ids-to-hide []] - (let [id (first ids-seq)] - (if (nil? id) - [ids-to-delete ids-to-hide] - (if (in-component-copy? id) - (recur (rest ids-seq) - ids-to-delete - (conj ids-to-hide id)) - (recur (rest ids-seq) - (conj ids-to-delete id) - ids-to-hide))))) - [ids []]) undo-id (or (:undo-id options) (js/Symbol)) - [changes all-parents] (-> (pcb/empty-changes it (:id page)) - (cflh/generate-delete-shapes file page objects ids-to-delete {:components-v2 components-v2 - :ignore-touched (:component-swap options) - :undo-group (:undo-group options) - :undo-id undo-id}))] + [all-parents changes] (-> (pcb/empty-changes it (:id page)) + (cflh/generate-delete-shapes file page objects ids {:components-v2 components-v2 + :ignore-touched (:component-swap options) + :undo-group (:undo-group options) + :undo-id undo-id}))] (rx/of (dwu/start-undo-transaction undo-id) - (update-shape-flags ids-to-hide {:hidden true :undo-group (:undo-group options)}) (dc/detach-comment-thread ids) (dch/commit-changes changes) (ptk/data-event :layout/update {:ids all-parents :undo-group (:undo-group options)})