diff --git a/common/src/app/common/geom/align.cljc b/common/src/app/common/geom/align.cljc index fc678e20e..11c8422c6 100644 --- a/common/src/app/common/geom/align.cljc +++ b/common/src/app/common/geom/align.cljc @@ -6,7 +6,9 @@ (ns app.common.geom.align (:require + [app.common.data :as d] [app.common.geom.shapes :as gsh] + [app.common.pages.helpers :refer [get-children]] [clojure.spec.alpha :as s])) ;; --- Alignment @@ -15,23 +17,13 @@ (declare calc-align-pos) -;; TODO: revisit on how to reuse code and dont have this function -;; duplicated because the implementation right now differs from the -;; original function. - -;; Duplicated from pages/helpers to remove cyclic dependencies -(defn- get-children [id objects] - (let [shapes (vec (get-in objects [id :shapes]))] - (if shapes - (into shapes (mapcat #(get-children % objects)) shapes) - []))) - (defn- recursive-move "Move the shape and all its recursive children." [shape dpoint objects] - (let [children-ids (get-children (:id shape) objects) - children (map #(get objects %) children-ids)] - (map #(gsh/move % dpoint) (cons shape children)))) + (->> (get-children (:id shape) objects) + (map (d/getf objects)) + (cons shape) + (map #(gsh/move % dpoint)))) (defn align-to-rect "Move the shape so that it is aligned with the given rectangle diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 7ff2aec82..56010f196 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -412,7 +412,7 @@ {:rotation angle :displacement displacement})) -(defn merge-modifiers* +(defn merge-modifiers [objects modifiers] (let [set-modifier @@ -422,8 +422,6 @@ (->> modifiers (reduce set-modifier objects)))) -(def merge-modifiers (memoize merge-modifiers*)) - (defn modifiers->transform ([modifiers] (modifiers->transform nil modifiers)) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 65dddbbdd..4fd822eca 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -99,37 +99,10 @@ [component] (get-in component [:objects (:id component)])) -;; Implemented with transient for performance -(defn get-children* - "Retrieve all children ids recursively for a given object. The - children's order will be breadth first." - [id objects] - (loop [result (transient []) - pending (transient []) - next id] - (let [children (get-in objects [next :shapes] []) - [result pending] - ;; Iterate through children and add them to the result - ;; also add them in pending to check for their children - (loop [result result - pending pending - current (first children) - children (rest children)] - (if current - (recur (conj! result current) - (conj! pending current) - (first children) - (rest children)) - [result pending])) - - ;; If we have still pending, advance the iterator - length (count pending)] - (if (pos? length) - (let [next (get pending (dec length))] - (recur result (pop! pending) next)) - (persistent! result))))) - -(def get-children (memoize get-children*)) +(defn get-children [id objects] + (if-let [shapes (-> (get objects id) :shapes (some-> vec))] + (into shapes (mapcat #(get-children % objects)) shapes) + [])) (defn get-children-objects "Retrieve all children objects recursively for a given object" @@ -175,7 +148,7 @@ shape (get objects (:frame-id shape)))) -(defn clean-loops* +(defn clean-loops "Clean a list of ids from circular references." [objects ids] @@ -192,8 +165,6 @@ (reduce add-element (d/ordered-set) ids))) -(def clean-loops (memoize clean-loops*)) - (defn calculate-invalid-targets [shape-id objects] (let [result #{shape-id} diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index eb31598aa..b76f47859 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -101,10 +101,12 @@ bool-shape (bool/bool-shape shape-wrapper)] (mf/fnc bool-wrapper [{:keys [shape] :as props}] - (let [childs (->> (cp/get-children (:id shape) objects) - (select-keys objects))] - [:& bool-shape {:shape shape - :childs childs}])))) + (let [childs (mf/use-memo + (mf/deps (:id shape) objects) + (fn [] + (->> (cp/get-children (:id shape) objects) + (select-keys objects))))] + [:& bool-shape {:shape shape :childs childs}])))) (defn svg-raw-wrapper-factory [objects] @@ -221,26 +223,39 @@ (mf/defc frame-svg {::mf/wrap [mf/memo]} [{:keys [objects frame zoom] :or {zoom 1} :as props}] - (let [modifier (-> (gpt/point (:x frame) (:y frame)) - (gpt/negate) - (gmt/translate-matrix)) - - frame-id (:id frame) - + (let [frame-id (:id frame) include-metadata? (mf/use-ctx export/include-metadata-ctx) - modifier-ids (concat [frame-id] (cp/get-children frame-id objects)) - update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) - objects (reduce update-fn objects modifier-ids) - frame (assoc-in frame [:modifiers :displacement] modifier) + modifier + (mf/use-memo + (mf/deps (:x frame) (:y frame)) + (fn [] + (-> (gpt/point (:x frame) (:y frame)) + (gpt/negate) + (gmt/translate-matrix)))) + + objects + (mf/use-memo + (mf/deps frame-id objects modifier) + (fn [] + (let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)] + (->> (cp/get-children frame-id objects) + (into [frame-id]) + (reduce update-fn objects))))) + + frame + (mf/use-memo + (mf/deps modifier) + (fn [] (assoc-in frame [:modifiers :displacement] modifier))) + + wrapper + (mf/use-memo + (mf/deps objects) + (fn [] (frame-wrapper-factory objects))) width (* (:width frame) zoom) height (* (:height frame) zoom) - vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)}) - - wrapper (mf/use-memo - (mf/deps objects) - #(frame-wrapper-factory objects))] + vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)})] [:svg {:view-box vbox :width (ust/format-precision width viewbox-decimal-precision) @@ -254,19 +269,25 @@ (mf/defc component-svg {::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]} [{:keys [objects group zoom] :or {zoom 1} :as props}] - (let [modifier (-> (gpt/point (:x group) (:y group)) - (gpt/negate) - (gmt/translate-matrix)) - - group-id (:id group) - + (let [group-id (:id group) include-metadata? (mf/use-ctx export/include-metadata-ctx) - modifier-ids (concat [group-id] (cp/get-children group-id objects)) + modifier + (mf/use-memo + (mf/deps (:x group) (:y group)) + (fn [] + (-> (gpt/point (:x group) (:y group)) + (gpt/negate) + (gmt/translate-matrix)))) - update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) - modifiers (reduce update-fn {} modifier-ids) - objects (gsh/merge-modifiers objects modifiers) + objects + (mf/use-memo + (mf/deps modifier objects group-id) + (fn [] + (let [modifier-ids (concat [group-id] (cp/get-children group-id objects)) + update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) + modifiers (reduce update-fn {} modifier-ids)] + (gsh/merge-modifiers objects modifiers)))) group (get objects group-id) width (* (:width group) zoom) @@ -276,7 +297,7 @@ group-wrapper (mf/use-memo (mf/deps objects) - #(group-wrapper-factory objects))] + (fn [] (group-wrapper-factory objects)))] [:svg {:view-box vbox :width (ust/format-precision width viewbox-decimal-precision) @@ -285,31 +306,51 @@ :xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")} + [:> shape-container {:shape group} [:& group-wrapper {:shape group :view-box vbox}]]])) (mf/defc component-symbol - [{:keys [id data] :as props}] + {::mf/wrap-props false} + [props] + (let [id (obj/get props "id") + data (obj/get props "data") + name (:name data) + path (:path data) + objects (:objects data) + root (get objects id) + selrect (:selrect root) - (let [{:keys [name path objects]} data - root (get objects id) + vbox + (format-viewbox + {:width (:width selrect) + :height (:height selrect)}) - {:keys [width height]} (:selrect root) - vbox (format-viewbox {:width width :height height}) + modifier + (mf/use-memo + (mf/deps (:x root) (:y root)) + (fn [] + (-> (gpt/point (:x root) (:y root)) + (gpt/negate) + (gmt/translate-matrix)))) - modifier (-> (gpt/point (:x root) (:y root)) - (gpt/negate) - (gmt/translate-matrix)) + objects + (mf/use-memo + (mf/deps modifier id objects) + (fn [] + (let [modifier-ids (concat [id] (cp/get-children id objects)) + update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)] + (reduce update-fn objects modifier-ids)))) - modifier-ids (concat [id] (cp/get-children id objects)) - update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) - objects (reduce update-fn objects modifier-ids) - root (assoc-in root [:modifiers :displacement] modifier) + root + (mf/use-memo + (mf/deps modifier root) + (fn [] (assoc-in root [:modifiers :displacement] modifier))) group-wrapper (mf/use-memo (mf/deps objects) - #(group-wrapper-factory objects))] + (fn [] (group-wrapper-factory objects)))] [:> "symbol" #js {:id (str id) :viewBox vbox @@ -321,10 +362,9 @@ (mf/defc components-sprite-svg {::mf/wrap-props false} [props] - - (let [data (obj/get props "data") - children (obj/get props "children") - embed? (obj/get props "embed?") + (let [data (obj/get props "data") + children (obj/get props "children") + embed? (obj/get props "embed?") include-metadata? (obj/get props "include-metadata?")] [:& (mf/provider embed/context) {:value embed?} [:& (mf/provider export/include-metadata-ctx) {:value include-metadata?} diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 3651012c0..fe79dd29e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -96,18 +96,21 @@ ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - thumbnail? (unchecked-get props "thumbnail?") + (when-let [shape (unchecked-get props "shape")] + (let [objects (unchecked-get props "objects") + thumbnail? (unchecked-get props "thumbnail?") - children (-> (mapv (d/getf objects) (:shapes shape)) - (hooks/use-equal-memo)) - all-children (-> (cp/get-children-objects (:id shape) objects) - (hooks/use-equal-memo)) + children + (-> (mapv (d/getf objects) (:shapes shape)) + (hooks/use-equal-memo)) - show-thumbnail? (and thumbnail? (some? (:thumbnail shape)))] + all-children + (-> (cp/get-children-objects (:id shape) objects) + (hooks/use-equal-memo)) + + show-thumbnail? + (and thumbnail? (some? (:thumbnail shape)))] - (when (some? shape) [:g.frame-wrapper {:display (when (:hidden shape) "none")} [:> shape-container {:shape shape} [:& ff/fontfaces-style {:shapes all-children}]