diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 12e3f7762..196307bc8 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -57,6 +57,14 @@ #?(:cljs (instance? lkm/LinkedMap o) :clj (instance? LinkedMap o))) +(defn vec2 + "Creates a optimized vector compatible type of length 2 backed + internally with MapEntry impl because it has faster access method + for its fields." + [o1 o2] + #?(:clj (clojure.lang.MapEntry. o1 o2) + :cljs (cljs.core/->MapEntry o1 o2 nil))) + #?(:clj (defmethod print-method clojure.lang.PersistentQueue [q, w] ;; Overload the printer for queues so they look like fish diff --git a/common/src/app/common/files/helpers.cljc b/common/src/app/common/files/helpers.cljc index 46b9ac66e..ea53bc2d6 100644 --- a/common/src/app/common/files/helpers.cljc +++ b/common/src/app/common/files/helpers.cljc @@ -484,7 +484,7 @@ (letfn [(red-fn [cur-idx id] (let [[prev-idx _] (first cur-idx) prev-idx (or prev-idx 0) - cur-idx (conj cur-idx [(inc prev-idx) id])] + cur-idx (conj cur-idx (d/vec2 (inc prev-idx) id))] (rec-index cur-idx id))) (rec-index [cur-idx id] (let [object (get objects id)] @@ -509,10 +509,11 @@ (defn order-by-indexed-shapes [objects ids] - (->> (indexed-shapes objects) - (sort-by first) - (filter (comp (into #{} ids) second)) - (map second))) + (let [ids (if (set? ids) ids (set ids))] + (->> (indexed-shapes objects) + (filter (fn [o] (contains? ids (val o)))) + (sort-by key) + (map val)))) (defn get-index-replacement "Given a collection of shapes, calculate their positions diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index bd750dee5..8b7f34aca 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -6,6 +6,7 @@ (ns app.common.files.libraries-helpers (:require + [app.common.data :as d] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.types.component :as ctk] @@ -37,41 +38,50 @@ use it as root. Otherwise, create a frame (v2) or group (v1) that contains all ids. Then, make a component with it, and link all shapes to their corresponding one in the component." [it shapes objects page-id file-id components-v2 prepare-create-group prepare-create-board] - (let [changes (pcb/empty-changes it page-id) - from-singe-frame? (and (= 1 (count shapes)) (-> shapes first cfh/frame-shape?)) + (let [changes (pcb/empty-changes it page-id) + shapes-count (count shapes) + first-shape (first shapes) + + from-singe-frame? + (and (= 1 shapes-count) + (cfh/frame-shape? first-shape)) + [root changes old-root-ids] - (if (and (= (count shapes) 1) - (or (and (= (:type (first shapes)) :group) (not components-v2)) - (= (:type (first shapes)) :frame)) - (not (ctk/instance-head? (first shapes)))) - - [(first shapes) + (if (and (= shapes-count 1) + (or (and (cfh/group-shape? first-shape) + (not components-v2)) + (cfh/frame-shape? first-shape)) + (not (ctk/instance-head? first-shape))) + [first-shape (-> (pcb/empty-changes it page-id) (pcb/with-objects objects)) - (:shapes (first shapes))] + (:shapes first-shape)] - (let [root-name (if (= 1 (count shapes)) - (:name (first shapes)) + (let [root-name (if (= 1 shapes-count) + (:name first-shape) "Component 1") - [root changes] (if-not components-v2 - (prepare-create-group it ; These functions needs to be passed as argument - objects ; to avoid a circular dependence - page-id - shapes - root-name - (not (ctk/instance-head? (first shapes)))) - (prepare-create-board changes - (uuid/next) - (:parent-id (first shapes)) - objects - (map :id shapes) - nil - root-name - true))] + shape-ids (into (d/ordered-set) (map :id) shapes) - [root changes (map :id shapes)])) + [root changes] + (if-not components-v2 + (prepare-create-group it ; These functions needs to be passed as argument + objects ; to avoid a circular dependence + page-id + shapes + root-name + (not (ctk/instance-head? first-shape))) + (prepare-create-board changes + (uuid/next) + (:parent-id first-shape) + objects + shape-ids + nil + root-name + true))] + + [root changes shape-ids])) changes (cond-> changes @@ -79,8 +89,7 @@ (pcb/update-shapes (:shapes root) (fn [shape] - (-> shape - (assoc :constraints-h :scale :constraints-v :scale))))) + (assoc shape :constraints-h :scale :constraints-v :scale)))) objects' (assoc objects (:id root) root) diff --git a/common/src/app/common/files/shapes_helpers.cljc b/common/src/app/common/files/shapes_helpers.cljc index 03e3e89c1..f9f814186 100644 --- a/common/src/app/common/files/shapes_helpers.cljc +++ b/common/src/app/common/files/shapes_helpers.cljc @@ -39,16 +39,17 @@ (defn prepare-move-shapes-into-frame [changes frame-id shapes objects] - (let [ordered-indexes (cfh/order-by-indexed-shapes objects shapes) - parent-id (get-in objects [frame-id :parent-id]) - ordered-indexes (->> ordered-indexes (remove #(= % parent-id))) - to-move-shapes (map (d/getf objects) ordered-indexes)] - (if (d/not-empty? to-move-shapes) + (let [parent-id (dm/get-in objects [frame-id :parent-id]) + shapes (remove #(= % parent-id) shapes) + to-move (->> shapes + (map (d/getf objects)) + (not-empty))] + (if to-move (-> changes (cond-> (not (ctl/any-layout? objects frame-id)) - (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) - (pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) - (pcb/change-parent frame-id to-move-shapes 0) + (pcb/update-shapes shapes ctl/remove-layout-item-data)) + (pcb/update-shapes shapes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) + (pcb/change-parent frame-id to-move 0) (cond-> (ctl/grid-layout? objects frame-id) (-> (pcb/update-shapes [frame-id] ctl/assign-cells {:with-objects? true}) (pcb/reorder-grid-children [frame-id])))) @@ -60,90 +61,102 @@ changes id parent-id objects selected index frame-name without-fill? nil)) ([changes id parent-id objects selected index frame-name without-fill? target-cell-id] - (let [selected-objs (map #(get objects %) selected) - new-index (or index - (cfh/get-index-replacement selected objects))] - (when (d/not-empty? selected) - (let [srect (gsh/shapes->rect selected-objs) - selected-id (first selected) + (when-let [selected-objs (->> selected + (map (d/getf objects)) + (not-empty))] - frame-id (dm/get-in objects [selected-id :frame-id]) - parent-id (or parent-id (dm/get-in objects [selected-id :parent-id])) - base-parent (get objects parent-id) + (let [;; We calculate here the ordered selection because it is used + ;; multiple times and this avoid the need of creating the index + ;; manytimes for single operation. + selected' (cfh/order-by-indexed-shapes objects selected) + new-index (or index + (->> (first selected') + (cfh/get-position-on-parent objects) + (inc))) - layout-props - (when (and (= 1 (count selected)) - (ctl/any-layout? base-parent)) - (let [shape (get objects selected-id)] - (select-keys shape ctl/layout-item-props))) + srect (gsh/shapes->rect selected-objs) + selected-id (first selected) + selected-obj (get objects selected-id) - target-cell-id - (if (and (nil? target-cell-id) - (ctl/grid-layout? objects parent-id)) - ;; Find the top-left grid cell of the selected elements - (let [ncols (count (:layout-grid-columns base-parent))] - (->> selected - (map #(ctl/get-cell-by-shape-id base-parent %)) - (apply min-key (fn [{:keys [row column]}] (+ (* ncols row) column))) - :id)) - target-cell-id) + frame-id (get selected-obj :frame-id) + parent-id (or parent-id (get selected-obj :parent-id)) + base-parent (get objects parent-id) - attrs {:type :frame - :x (:x srect) - :y (:y srect) - :width (:width srect) - :height (:height srect)} + layout-props + (when (and (= 1 (count selected)) + (ctl/any-layout? base-parent)) + (select-keys selected-obj ctl/layout-item-props)) - shape (cts/setup-shape - (cond-> attrs - (some? id) - (assoc :id id) + target-cell-id + (if (and (nil? target-cell-id) + (ctl/grid-layout? objects parent-id)) + ;; Find the top-left grid cell of the selected elements + (let [ncols (count (:layout-grid-columns base-parent))] + (->> selected + (map #(ctl/get-cell-by-shape-id base-parent %)) + (apply min-key (fn [{:keys [row column]}] (+ (* ncols row) column))) + :id)) + target-cell-id) - (some? frame-name) - (assoc :name frame-name) + attrs + {:type :frame + :x (:x srect) + :y (:y srect) + :width (:width srect) + :height (:height srect)} - :always - (assoc :frame-id frame-id - :parent-id parent-id - :shapes (into [] selected)) + shape + (cts/setup-shape + (cond-> attrs + (some? id) + (assoc :id id) - (some? layout-props) - (d/patch-object layout-props) + (some? frame-name) + (assoc :name frame-name) - (or (not= frame-id uuid/zero) without-fill?) - (assoc :fills [] :hide-in-viewer true))) + :always + (assoc :frame-id frame-id + :parent-id parent-id + :shapes (into [] selected)) - shape (with-meta shape {:index new-index}) + (some? layout-props) + (d/patch-object layout-props) - [shape changes] - (prepare-add-shape changes shape objects) + (or (not= frame-id uuid/zero) without-fill?) + (assoc :fills [] :hide-in-viewer true))) - changes - (prepare-move-shapes-into-frame changes (:id shape) selected objects) + shape + (with-meta shape {:index new-index}) - changes - (cond-> changes - (ctl/grid-layout? objects (:parent-id shape)) - (-> (pcb/update-shapes - [(:parent-id shape)] - (fn [parent objects] - ;; This restores the grid layout before adding and moving the shapes - ;; this is done because the add+move could have altered the layout and we - ;; want to do it after both operations are completed. Also here we could - ;; asign the new element to a target-cell - (-> parent - (assoc :layout-grid-cells (:layout-grid-cells base-parent)) - (assoc :layout-grid-rows (:layout-grid-rows base-parent)) - (assoc :layout-grid-columns (:layout-grid-columns base-parent)) + [shape changes] + (prepare-add-shape changes shape objects) - (cond-> (some? target-cell-id) - (assoc-in [:layout-grid-cells target-cell-id :shapes] [(:id shape)])) - (ctl/assign-cells objects))) - {:with-objects? true}) + changes + (prepare-move-shapes-into-frame changes (:id shape) selected' objects) - (pcb/reorder-grid-children [(:parent-id shape)])))] + changes + (cond-> changes + (ctl/grid-layout? objects (:parent-id shape)) + (-> (pcb/update-shapes + [(:parent-id shape)] + (fn [parent objects] + ;; This restores the grid layout before adding and moving the shapes + ;; this is done because the add+move could have altered the layout and we + ;; want to do it after both operations are completed. Also here we could + ;; asign the new element to a target-cell + (-> parent + (assoc :layout-grid-cells (:layout-grid-cells base-parent)) + (assoc :layout-grid-rows (:layout-grid-rows base-parent)) + (assoc :layout-grid-columns (:layout-grid-columns base-parent)) - [shape changes]))))) + (cond-> (some? target-cell-id) + (assoc-in [:layout-grid-cells target-cell-id :shapes] [(:id shape)])) + (ctl/assign-cells objects))) + {:with-objects? true}) + + (pcb/reorder-grid-children [(:parent-id shape)])))] + + [shape changes])))) (defn prepare-create-empty-artboard diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 6e47b0a13..5288239ac 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -72,13 +72,15 @@ (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - shapes (->> shapes (remove #(dm/get-in objects [% :blocked]))) + shapes (->> shapes + (remove #(dm/get-in objects [% :blocked])) + (cfh/order-by-indexed-shapes objects)) + changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects)) - changes (cfsh/prepare-move-shapes-into-frame changes - frame-id - shapes - objects)] + + changes (cfsh/prepare-move-shapes-into-frame changes frame-id shapes objects)] + (if (some? changes) (rx/of (dch/commit-changes changes)) (rx/empty))))))