From c14ece9f8d82fa13236a05cc09f342376702397a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 11 May 2022 13:44:47 +0200 Subject: [PATCH] :bug: Fix problems with thumbnails --- .../app/main/data/workspace/thumbnails.cljs | 5 +- frontend/src/app/main/ui/shapes/frame.cljs | 43 ++-- .../app/main/ui/workspace/shapes/frame.cljs | 19 +- .../shapes/frame/dynamic_modifiers.cljs | 221 +++++++++++++++++- .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../app/main/ui/workspace/viewport/hooks.cljs | 17 +- .../app/main/ui/workspace/viewport/utils.cljs | 214 ----------------- 7 files changed, 271 insertions(+), 250 deletions(-) diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 4c574bde8..8a5c117ff 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -153,7 +153,8 @@ (ptk/reify ::duplicate-thumbnail ptk/UpdateEvent (update [_ state] - (let [old-shape-thumbnail (get-in state [:workspace-file :thumbnails old-id])] - (-> state (assoc-in [:workspace-file :thumbnails new-id] old-shape-thumbnail)))))) + (let [page-id (get state :current-page-id) + old-shape-thumbnail (get-in state [:workspace-file :thumbnails (dm/str page-id old-id)])] + (-> state (assoc-in [:workspace-file :thumbnails (dm/str page-id new-id)] old-shape-thumbnail)))))) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 91425cd79..168e26893 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -55,17 +55,26 @@ :width width :height height :className "frame-background"})) - path? (some? (.-d props))] + path? (some? (.-d props)) + render-id (mf/use-ctx muc/render-ctx)] + [:* - [:image.frame-thumbnail - {:id (dm/str "thumbnail-" (:id shape)) - :xlinkHref (:thumbnail shape) - :x (:x shape) - :y (:y shape) - :width (:width shape) - :height (:height shape) - ;; DEBUG - :style {:filter (when (debug? :thumbnails) "sepia(1)")}}] + [:g {:clip-path (frame-clip-url shape render-id)} + [:& frame-clip-def {:shape shape :render-id render-id}] + [:& shape-fills {:shape shape} + (if path? + [:> :path props] + [:> :rect props])] + + [:image.frame-thumbnail + {:id (dm/str "thumbnail-" (:id shape)) + :xlinkHref (:thumbnail shape) + :x (:x shape) + :y (:y shape) + :width (:width shape) + :height (:height shape) + ;; DEBUG + :style {:filter (when (debug? :thumbnails) "sepia(1)")}}]] [:& shape-strokes {:shape shape} (if path? @@ -96,16 +105,16 @@ [:* [:g {:clip-path (frame-clip-url shape render-id)} - [:* - [:& shape-fills {:shape shape} - (if path? - [:> :path props] - [:> :rect props])] + [:& shape-fills {:shape shape} + (if path? + [:> :path props] + [:> :rect props])] + [:g.frame-children (for [item childs] [:& shape-wrapper {:shape item - :key (dm/str (:id item))}]) - ]] + :key (dm/str (:id item))}])]] + [:& shape-strokes {:shape shape} (if path? [:> :path props] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 254d6a87e..a2b71dee7 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.uuid :as uuid] [app.main.data.workspace.thumbnails :as dwt] [app.main.refs :as refs] [app.main.ui.context :as ctx] @@ -61,6 +62,7 @@ thumbnail? (unchecked-get props "thumbnail?") objects (unchecked-get props "objects") + render-id (mf/use-memo #(str (uuid/next))) fonts (mf/use-memo (mf/deps shape objects) #(ff/shape->fonts shape objects)) fonts (-> fonts (hooks/use-equal-memo)) @@ -73,7 +75,7 @@ thumbnail-data-ref (mf/use-memo (mf/deps page-id frame-id) #(refs/thumbnail-frame-data page-id frame-id)) thumbnail-data (mf/deref thumbnail-data-ref) - thumbnail? (and thumbnail? (or (some? (:thumbnail shape)) (some? thumbnail-data))) + thumbnail? (and thumbnail? (some? thumbnail-data)) ;; References to the current rendered node and the its parentn node-ref (mf/use-var nil) @@ -117,11 +119,12 @@ @node-ref) (when (not @rendered?) (reset! rendered? true))))) - [:g.frame-container {:key "frame-container" :ref on-frame-load} - [:g.frame-thumbnail {:id (dm/str "thumbnail-container-" (:id shape))} - [:> frame/frame-thumbnail {:key (dm/str (:id shape)) - :shape (cond-> shape - (some? thumbnail-data) - (assoc :thumbnail thumbnail-data))}] + [:& (mf/provider ctx/render-ctx) {:value render-id} + [:g.frame-container {:key "frame-container" :ref on-frame-load} + [:g.frame-thumbnail-wrapper {:id (dm/str "thumbnail-container-" (:id shape))} + [:> frame/frame-thumbnail {:key (dm/str (:id shape)) + :shape (cond-> shape + (some? thumbnail-data) + (assoc :thumbnail thumbnail-data))}] - thumb-renderer]])))) + thumb-renderer]]])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 3624c041e..9ec66d7d6 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -7,10 +7,223 @@ (ns app.main.ui.workspace.shapes.frame.dynamic-modifiers (:require [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.main.ui.workspace.viewport.utils :as utils] + [app.util.dom :as dom] [rumext.alpha :as mf])) +(defn- transform-no-resize + "If we apply a scale directly to the texts it will show deformed so we need to create this + correction matrix to \"undo\" the resize but keep the other transformations." + [{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers] + + (let [corner-pt (first points) + corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse)) + + resize-x? (some? (:resize-vector modifiers)) + resize-y? (some? (:resize-vector-2 modifiers)) + + flip-x? (neg? (get-in modifiers [:resize-vector :x])) + flip-y? (or (neg? (get-in modifiers [:resize-vector :y])) + (neg? (get-in modifiers [:resize-vector-2 :y]))) + + result (cond-> (gmt/matrix) + (and (some? transform) (or resize-x? resize-y?)) + (gmt/multiply transform) + + resize-x? + (gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt) + + resize-y? + (gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt) + + flip-x? + (gmt/scale (gpt/point -1 1) corner-pt) + + flip-y? + (gmt/scale (gpt/point 1 -1) corner-pt) + + (and (some? transform) (or resize-x? resize-y?)) + (gmt/multiply transform-inverse)) + + [width height] + (if (or resize-x? resize-y?) + (let [pc (cond-> (gpt/point x y) + (some? transform) + (gpt/transform transform) + + (some? current-transform) + (gpt/transform current-transform)) + + pw (cond-> (gpt/point (+ x width) y) + (some? transform) + (gpt/transform transform) + + (some? current-transform) + (gpt/transform current-transform)) + + ph (cond-> (gpt/point x (+ y height)) + (some? transform) + (gpt/transform transform) + + (some? current-transform) + (gpt/transform current-transform))] + [(gpt/distance pc pw) (gpt/distance pc ph)]) + [width height])] + + [result width height])) + +(defn get-nodes + "Retrieve the DOM nodes to apply the matrix transformation" + [base-node {:keys [id type masked-group?]}] + (let [shape-node (dom/query base-node (str "#shape-" id)) + + frame? (= :frame type) + group? (= :group type) + text? (= :text type) + mask? (and group? masked-group?)] + + (cond + frame? + [shape-node + (dom/query shape-node ".frame-children") + (dom/query (str "#thumbnail-container-" id)) + (dom/query (str "#thumbnail-" id))] + + ;; For groups we don't want to transform the whole group but only + ;; its filters/masks + mask? + [(dom/query shape-node ".mask-clip-path") + (dom/query shape-node ".mask-shape")] + + group? + (let [shape-defs (dom/query shape-node "defs")] + (d/concat-vec + (dom/query-all shape-defs ".svg-def") + (dom/query-all shape-defs ".svg-mask-wrapper"))) + + text? + [shape-node + (dom/query shape-node ".text-shape")] + + :else + [shape-node]))) + +(defn transform-region! + [node modifiers] + + (let [{:keys [x y width height]} + (-> (gsh/make-selrect + (-> (dom/get-attribute node "data-old-x") d/parse-double) + (-> (dom/get-attribute node "data-old-y") d/parse-double) + (-> (dom/get-attribute node "data-old-width") d/parse-double) + (-> (dom/get-attribute node "data-old-height") d/parse-double)) + (gsh/transform-selrect modifiers))] + (dom/set-attribute! node "x" x) + (dom/set-attribute! node "y" y) + (dom/set-attribute! node "width" width) + (dom/set-attribute! node "height" height))) + +(defn start-transform! + [base-node shapes] + (doseq [shape shapes] + (when-let [nodes (get-nodes base-node shape)] + (doseq [node nodes] + (let [old-transform (dom/get-attribute node "transform")] + (when (some? old-transform) + (dom/set-attribute! node "data-old-transform" old-transform)) + + (when (or (= (dom/get-tag-name node) "linearGradient") + (= (dom/get-tag-name node) "radialGradient")) + (let [gradient-transform (dom/get-attribute node "gradientTransform")] + (when (some? gradient-transform) + (dom/set-attribute! node "data-old-gradientTransform" gradient-transform)))) + + (when (= (dom/get-tag-name node) "pattern") + (let [pattern-transform (dom/get-attribute node "patternTransform")] + (when (some? pattern-transform) + (dom/set-attribute! node "data-old-patternTransform" pattern-transform)))) + + (when (or (= (dom/get-tag-name node) "mask") + (= (dom/get-tag-name node) "filter")) + (let [old-x (dom/get-attribute node "x") + old-y (dom/get-attribute node "y") + old-width (dom/get-attribute node "width") + old-height (dom/get-attribute node "height")] + (dom/set-attribute! node "data-old-x" old-x) + (dom/set-attribute! node "data-old-y" old-y) + (dom/set-attribute! node "data-old-width" old-width) + (dom/set-attribute! node "data-old-height" old-height)))))))) + +(defn set-transform-att! + [node att value] + + (let [old-att (dom/get-attribute node (dm/str "data-old-" att)) + new-value (if (some? old-att) + (dm/str value " " old-att) + (str value))] + (dom/set-attribute! node att (str new-value)))) + +(defn update-transform! + [base-node shapes transforms modifiers] + (doseq [{:keys [id] :as shape} shapes] + (when-let [nodes (get-nodes base-node shape)] + (let [transform (get transforms id) + modifiers (get-in modifiers [id :modifiers])] + + (doseq [node nodes] + (cond + ;; Text shapes need special treatment because their resize only change + ;; the text area, not the change size/position + (or (dom/class? node "text-shape") + (dom/class? node "frame-thumbnail")) + (let [[transform] (transform-no-resize shape transform modifiers)] + (set-transform-att! node "transform" transform)) + + (dom/class? node "frame-children") + (set-transform-att! node "transform" (gmt/inverse transform)) + + (or (= (dom/get-tag-name node) "mask") + (= (dom/get-tag-name node) "filter")) + (transform-region! node modifiers) + + (or (= (dom/get-tag-name node) "linearGradient") + (= (dom/get-tag-name node) "radialGradient")) + (set-transform-att! node "gradientTransform" transform) + + (= (dom/get-tag-name node) "pattern") + (set-transform-att! node "patternTransform" transform) + + (and (some? transform) (some? node)) + (set-transform-att! node "transform" transform))))))) + +(defn remove-transform! + [base-node shapes] + (doseq [shape shapes] + (when-let [nodes (get-nodes base-node shape)] + (doseq [node nodes] + (when (some? node) + (cond + (= (dom/get-tag-name node) "foreignObject") + ;; The shape width/height will be automaticaly setup when the modifiers are applied + nil + + (or (= (dom/get-tag-name node) "mask") + (= (dom/get-tag-name node) "filter")) + (do + (dom/remove-attribute! node "data-old-x") + (dom/remove-attribute! node "data-old-y") + (dom/remove-attribute! node "data-old-width") + (dom/remove-attribute! node "data-old-height")) + + :else + (let [old-transform (dom/get-attribute node "data-old-transform")] + (if (some? old-transform) + (dom/remove-attribute! node "data-old-transform") + (dom/remove-attribute! node "transform"))))))))) + (defn use-dynamic-modifiers [objects node modifiers] @@ -42,13 +255,13 @@ is-cur-val? (d/not-empty? modifiers)] (when (and (not is-prev-val?) is-cur-val?) - (utils/start-transform! node shapes)) + (start-transform! node shapes)) (when is-cur-val? - (utils/update-transform! node shapes transforms modifiers)) + (update-transform! node shapes transforms modifiers)) (when (and is-prev-val? (not is-cur-val?)) - (utils/remove-transform! node @prev-shapes)) + (remove-transform! node @prev-shapes)) (reset! prev-modifiers modifiers) (reset! prev-transforms transforms) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index cb45c78b3..3abcde449 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -183,7 +183,7 @@ (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids @hover-disabled? focus zoom) (hooks/setup-viewport-modifiers modifiers base-objects) (hooks/setup-shortcuts node-editing? drawing-path?) - (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom) + (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform) [:div.viewport [:div.viewport-overlays {:ref overlays-ref} diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 9f656d1de..8cd4fda9c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -219,20 +219,29 @@ (gsh/overlaps? frame vbox)))) (defn setup-active-frames - [objects hover-ids selected active-frames zoom] + [objects hover-ids selected active-frames zoom transform] (mf/use-effect - (mf/deps objects @hover-ids selected zoom) + (mf/deps objects @hover-ids selected zoom transform) (fn [] (when (some? @hover-ids) (let [hover-frame (when (> zoom 0.25) (last @hover-ids)) new-active-frames (if (some? hover-frame) #{hover-frame} #{}) + + frame? #(= :frame (get-in objects [% :type])) + + selected-frames (->> selected (filter frame?)) + new-active-frames + (cond-> new-active-frames + (and (not= transform :move) (= (count selected-frames) 1)) + (conj new-active-frames (first selected-frames))) + new-active-frames (into new-active-frames (comp - (filter #(not= :frame (get-in objects [% :type]))) + (remove frame?) (map #(get-in objects [% :frame-id]))) - selected) ] + selected)] (reset! active-frames new-active-frames)))))) ;; NOTE: this is executed on each page change, maybe we need to move diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index fe5c4586e..7f29d4776 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -8,224 +8,10 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] [app.main.ui.cursors :as cur] [app.util.dom :as dom])) -(defn- text-corrected-transform - "If we apply a scale directly to the texts it will show deformed so we need to create this - correction matrix to \"undo\" the resize but keep the other transformations." - [{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers] - - (let [corner-pt (first points) - corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse)) - - resize-x? (some? (:resize-vector modifiers)) - resize-y? (some? (:resize-vector-2 modifiers)) - - flip-x? (neg? (get-in modifiers [:resize-vector :x])) - flip-y? (or (neg? (get-in modifiers [:resize-vector :y])) - (neg? (get-in modifiers [:resize-vector-2 :y]))) - - result (cond-> (gmt/matrix) - (and (some? transform) (or resize-x? resize-y?)) - (gmt/multiply transform) - - resize-x? - (gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt) - - resize-y? - (gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt) - - flip-x? - (gmt/scale (gpt/point -1 1) corner-pt) - - flip-y? - (gmt/scale (gpt/point 1 -1) corner-pt) - - (and (some? transform) (or resize-x? resize-y?)) - (gmt/multiply transform-inverse)) - - [width height] - (if (or resize-x? resize-y?) - (let [pc (cond-> (gpt/point x y) - (some? transform) - (gpt/transform transform) - - (some? current-transform) - (gpt/transform current-transform)) - - pw (cond-> (gpt/point (+ x width) y) - (some? transform) - (gpt/transform transform) - - (some? current-transform) - (gpt/transform current-transform)) - - ph (cond-> (gpt/point x (+ y height)) - (some? transform) - (gpt/transform transform) - - (some? current-transform) - (gpt/transform current-transform))] - [(gpt/distance pc pw) (gpt/distance pc ph)]) - [width height])] - - [result width height])) - -(defn get-nodes - "Retrieve the DOM nodes to apply the matrix transformation" - [base-node {:keys [id type masked-group?]}] - (let [shape-node (dom/query base-node (str "#shape-" id)) - - frame? (= :frame type) - group? (= :group type) - text? (= :text type) - mask? (and group? masked-group?) - - ;; When the shape is a frame we maybe need to move its thumbnail - thumb-node (when frame? (dom/query (str "#thumbnail-container-" id)))] - - (cond - frame? - [thumb-node - (dom/get-parent (dom/query shape-node ".frame-background")) - (dom/query shape-node ".frame-clip")] - - ;; For groups we don't want to transform the whole group but only - ;; its filters/masks - mask? - [(dom/query shape-node ".mask-clip-path") - (dom/query shape-node ".mask-shape")] - - group? - (let [shape-defs (dom/query shape-node "defs")] - (d/concat-vec - (dom/query-all shape-defs ".svg-def") - (dom/query-all shape-defs ".svg-mask-wrapper"))) - - text? - [shape-node - (dom/query shape-node ".text-shape")] - - :else - [shape-node]))) - -(defn transform-region! - [node modifiers] - - (let [{:keys [x y width height]} - (-> (gsh/make-selrect - (-> (dom/get-attribute node "data-old-x") d/parse-double) - (-> (dom/get-attribute node "data-old-y") d/parse-double) - (-> (dom/get-attribute node "data-old-width") d/parse-double) - (-> (dom/get-attribute node "data-old-height") d/parse-double)) - (gsh/transform-selrect modifiers))] - (dom/set-attribute! node "x" x) - (dom/set-attribute! node "y" y) - (dom/set-attribute! node "width" width) - (dom/set-attribute! node "height" height))) - -(defn start-transform! - [base-node shapes] - (doseq [shape shapes] - (when-let [nodes (get-nodes base-node shape)] - (doseq [node nodes] - (let [old-transform (dom/get-attribute node "transform")] - (when (some? old-transform) - (dom/set-attribute! node "data-old-transform" old-transform)) - - (when (or (= (dom/get-tag-name node) "linearGradient") - (= (dom/get-tag-name node) "radialGradient")) - (let [gradient-transform (dom/get-attribute node "gradientTransform")] - (when (some? gradient-transform) - (dom/set-attribute! node "data-old-gradientTransform" gradient-transform)))) - - (when (= (dom/get-tag-name node) "pattern") - (let [pattern-transform (dom/get-attribute node "patternTransform")] - (when (some? pattern-transform) - (dom/set-attribute! node "data-old-patternTransform" pattern-transform)))) - - (when (or (= (dom/get-tag-name node) "mask") - (= (dom/get-tag-name node) "filter")) - (let [old-x (dom/get-attribute node "x") - old-y (dom/get-attribute node "y") - old-width (dom/get-attribute node "width") - old-height (dom/get-attribute node "height")] - (dom/set-attribute! node "data-old-x" old-x) - (dom/set-attribute! node "data-old-y" old-y) - (dom/set-attribute! node "data-old-width" old-width) - (dom/set-attribute! node "data-old-height" old-height)))))))) - -(defn set-transform-att! - [node att value] - - (let [old-att (dom/get-attribute node (dm/str "data-old-" att)) - new-value (if (some? old-att) - (dm/str value " " old-att) - (str value))] - (dom/set-attribute! node att (str new-value)))) - -(defn update-transform! - [base-node shapes transforms modifiers] - (doseq [{:keys [id type] :as shape} shapes] - (when-let [nodes (get-nodes base-node shape)] - (let [transform (get transforms id) - modifiers (get-in modifiers [id :modifiers]) - - [text-transform _text-width _text-height] - (when (= :text type) - (text-corrected-transform shape transform modifiers))] - - (doseq [node nodes] - (cond - ;; Text shapes need special treatment because their resize only change - ;; the text area, not the change size/position - (dom/class? node "text-shape") - (when (some? text-transform) - (set-transform-att! node "transform" text-transform)) - - (or (= (dom/get-tag-name node) "mask") - (= (dom/get-tag-name node) "filter")) - (transform-region! node modifiers) - - (or (= (dom/get-tag-name node) "linearGradient") - (= (dom/get-tag-name node) "radialGradient")) - (set-transform-att! node "gradientTransform" transform) - - (= (dom/get-tag-name node) "pattern") - (set-transform-att! node "patternTransform" transform) - - (and (some? transform) (some? node)) - (set-transform-att! node "transform" transform))))))) - -(defn remove-transform! - [base-node shapes] - (doseq [shape shapes] - (when-let [nodes (get-nodes base-node shape)] - (doseq [node nodes] - (when (some? node) - (cond - (= (dom/get-tag-name node) "foreignObject") - ;; The shape width/height will be automaticaly setup when the modifiers are applied - nil - - (or (= (dom/get-tag-name node) "mask") - (= (dom/get-tag-name node) "filter")) - (do - (dom/remove-attribute! node "data-old-x") - (dom/remove-attribute! node "data-old-y") - (dom/remove-attribute! node "data-old-width") - (dom/remove-attribute! node "data-old-height")) - - :else - (let [old-transform (dom/get-attribute node "data-old-transform")] - (if (some? old-transform) - (dom/remove-attribute! node "data-old-transform") - (dom/remove-attribute! node "transform"))))))))) - (defn format-viewbox [vbox] (dm/str (:x vbox 0) " " (:y vbox 0) " "