diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index c0748fe45..44a8595a9 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -20,6 +20,13 @@ [goog.functions :as f] [rumext.v2 :as mf])) +(def ^:private render-id 0) + +(defn use-render-id + "Get a stable, DOM usable identifier across all react rerenders" + [] + (mf/useMemo #(js* "\"render-\" + (++~{})" render-id) #js [])) + (defn use-rxsub [ob] (let [[state reset-state!] (mf/useState #(if (satisfies? IDeref ob) @ob nil))] diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 8ea32ef4e..cbd5e81fd 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -144,6 +144,7 @@ (:opacity shape) (obj/set! "opacity" (:opacity shape)))) +;; FIXME: DEPRECATED (defn extract-svg-attrs [render-id svg-defs svg-attrs] (if (and (empty? svg-defs) (empty? svg-attrs)) @@ -158,9 +159,26 @@ (dissoc :id)) attrs (-> svg-attrs (dissoc :style) (clj->js)) - styles (-> svg-attrs (:style {}) (clj->js))] + styles (-> svg-attrs (get :style {}) (clj->js))] + [attrs styles]))) +(defn get-svg-attrs + [shape render-id] + (let [svg-attrs (get shape :svg-attrs {}) + svg-defs (get shape :svg-defs {})] + (if (and (empty? svg-defs) + (empty? svg-attrs)) + {} + (let [replace-id (fn [id] + (if (contains? svg-defs id) + (str render-id "-" id) + id))] + (-> svg-attrs + (usvg/clean-attrs) + (usvg/update-attr-ids replace-id) + (dissoc :id)))))) + (defn add-style-attrs ([props shape] (let [render-id (mf/use-ctx muc/render-id)] @@ -226,19 +244,15 @@ (-> (obj/create) (add-style-attrs shape render-id)))) -(defn extract-fill-attrs - [fill-data render-id index type] - (let [fill-styles (-> (obj/get fill-data "style" (obj/create)) - (add-fill fill-data render-id index type))] - (-> (obj/create) - (obj/set! "style" fill-styles)))) - -(defn extract-stroke-attrs +(defn get-stroke-style [stroke-data index render-id] - (let [stroke-styles (-> (obj/get stroke-data "style" (obj/create)) - (add-stroke stroke-data render-id index))] - (-> (obj/create) - (obj/set! "style" stroke-styles)))) + ;; FIXME: optimize + (add-stroke #js {} stroke-data render-id index)) + +(defn get-fill-style + [fill-data index render-id type] + ;; FIXME: optimize + (add-fill #js {} fill-data render-id index type)) (defn extract-border-radius-attrs [shape] diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 64867e28e..fa4085158 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -20,188 +20,204 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) -(defn add-props - [props new-props] - (-> props - (obj/merge (clj->js new-props)))) - -(defn add-style - [props new-style] - (let [old-style (obj/get props "style") - style (obj/merge old-style (clj->js new-style))] - (-> props (obj/merge #js {:style style})))) - (mf/defc inner-stroke-clip-path + {::mf/wrap-props false} [{:keys [shape render-id index]}] - (let [suffix (if index (str "-" index) "") - clip-id (str "inner-stroke-" render-id "-" (:id shape) suffix) - shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix)] + (let [shape-id (dm/get-prop shape :id) + suffix (if (some? index) (dm/str "-" index) "") + clip-id (dm/str "inner-stroke-" render-id "-" shape-id suffix) + href (dm/str "#stroke-shape-" render-id "-" shape-id suffix)] [:> "clipPath" #js {:id clip-id} - [:use {:href (str "#" shape-id)}]])) + [:use {:href href}]])) (mf/defc outer-stroke-mask + {::mf/wrap-props false} [{:keys [shape stroke render-id index]}] - (let [suffix (if index (str "-" index) "") - stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix) - shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix) - stroke-width (case (:stroke-alignment stroke :center) - :center (/ (:stroke-width stroke 0) 2) - :outer (:stroke-width stroke 0) - 0) - margin (gsb/shape-stroke-margin stroke stroke-width) + (let [shape-id (dm/get-prop shape :id) + suffix (if (some? index) (dm/str "-" index) "") + mask-id (dm/str "outer-stroke-" render-id "-" shape-id suffix) + shape-id (dm/str "stroke-shape-" render-id "-" shape-id suffix) + href (dm/str "#" shape-id) - selrect - (if (cph/text-shape? shape) - (gst/shape->rect shape) - (grc/points->rect (:points shape))) + stroke-width (case (:stroke-alignment stroke :center) + :center (/ (:stroke-width stroke 0) 2) + :outer (:stroke-width stroke 0) + 0) + margin (gsb/shape-stroke-margin stroke stroke-width) - bounding-box - (-> selrect - (update :x - (+ stroke-width margin)) - (update :y - (+ stroke-width margin)) - (update :width + (* 2 (+ stroke-width margin))) - (update :height + (* 2 (+ stroke-width margin))) - (grc/update-rect :position))] + ;; NOTE: for performance reasons we may can delimit a bit the + ;; dependencies to really useful shape attrs instead of using + ;; the shepe as-is. + selrect (mf/with-memo [shape] + (if (cph/text-shape? shape) + (gst/shape->rect shape) + (grc/points->rect (:points shape)))) - [:mask {:id stroke-mask-id - :x (:x bounding-box) - :y (:y bounding-box) - :width (:width bounding-box) - :height (:height bounding-box) + stroke-margin (+ stroke-width margin) + + x (- (dm/get-prop selrect :x) stroke-margin) + y (- (dm/get-prop selrect :y) stroke-margin) + w (+ (dm/get-prop selrect :width) (* 2 stroke-margin)) + h (+ (dm/get-prop selrect :height) (* 2 stroke-margin))] + + [:mask {:id mask-id + :x x + :y y + :width w + :height h :maskUnits "userSpaceOnUse"} - [:use {:href (str "#" shape-id) - :style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}] + [:use + {:href href + :style {:fill "none" + :stroke "white" + :strokeWidth (* stroke-width 2)}}] - [:use {:href (str "#" shape-id) - :style #js {:fill "black" - :stroke "none"}}]])) + [:use + {:href href + :style {:fill "black" + :stroke "none"}}]])) (mf/defc cap-markers + {::mf/wrap-props false} [{:keys [stroke render-id index]}] - (let [marker-id-prefix (str "marker-" render-id) + (let [id-prefix (dm/str "marker-" render-id) + + gradient (:stroke-color-gradient stroke) cap-start (:stroke-cap-start stroke) cap-end (:stroke-cap-end stroke) - stroke-color (if (:stroke-color-gradient stroke) - (str/format "url(#%s)" (str "stroke-color-gradient_" render-id "_" index)) - (:stroke-color stroke)) + color (if (some? gradient) + (str/ffmt "url(#stroke-color-gradient_%s_%s)" render-id index) + (:stroke-color stroke)) - stroke-opacity (when-not (:stroke-color-gradient stroke) - (:stroke-opacity stroke))] + opacity (when-not (some? gradient) + (:stroke-opacity stroke))] [:* - (when (or (= cap-start :line-arrow) (= cap-end :line-arrow)) - [:marker {:id (str marker-id-prefix "-line-arrow") + (when (or (= cap-start :line-arrow) + (= cap-end :line-arrow)) + [:marker {:id (dm/str id-prefix "-line-arrow") :viewBox "0 0 3 6" :refX "2" :refY "3" :markerWidth "8.5" :markerHeight "8.5" :orient "auto-start-reverse" - :fill stroke-color - :fillOpacity stroke-opacity} + :fill color + :fillOpacity opacity} [:path {:d "M 0.5 0.5 L 3 3 L 0.5 5.5 L 0 5 L 2 3 L 0 1 z"}]]) - (when (or (= cap-start :triangle-arrow) (= cap-end :triangle-arrow)) - [:marker {:id (str marker-id-prefix "-triangle-arrow") + (when (or (= cap-start :triangle-arrow) + (= cap-end :triangle-arrow)) + [:marker {:id (dm/str id-prefix "-triangle-arrow") :viewBox "0 0 3 6" :refX "2" :refY "3" :markerWidth "8.5" :markerHeight "8.5" :orient "auto-start-reverse" - :fill stroke-color - :fillOpacity stroke-opacity} + :fill color + :fillOpacity opacity} [:path {:d "M 0 0 L 3 3 L 0 6 z"}]]) - (when (or (= cap-start :square-marker) (= cap-end :square-marker)) - [:marker {:id (str marker-id-prefix "-square-marker") + (when (or (= cap-start :square-marker) + (= cap-end :square-marker)) + [:marker {:id (dm/str id-prefix "-square-marker") :viewBox "0 0 6 6" :refX "3" :refY "3" :markerWidth "4.2426" ;; diagonal length of a 3x3 square :markerHeight "4.2426" :orient "auto-start-reverse" - :fill stroke-color - :fillOpacity stroke-opacity} + :fill color + :fillOpacity opacity} [:rect {:x 0 :y 0 :width 6 :height 6}]]) - (when (or (= cap-start :circle-marker) (= cap-end :circle-marker)) - [:marker {:id (str marker-id-prefix "-circle-marker") + (when (or (= cap-start :circle-marker) + (= cap-end :circle-marker)) + [:marker {:id (dm/str id-prefix "-circle-marker") :viewBox "0 0 6 6" :refX "3" :refY "3" :markerWidth "4" :markerHeight "4" :orient "auto-start-reverse" - :fill stroke-color - :fillOpacity stroke-opacity} + :fill color + :fillOpacity opacity} [:circle {:cx "3" :cy "3" :r "3"}]]) - (when (or (= cap-start :diamond-marker) (= cap-end :diamond-marker)) - [:marker {:id (str marker-id-prefix "-diamond-marker") + (when (or (= cap-start :diamond-marker) + (= cap-end :diamond-marker)) + [:marker {:id (dm/str id-prefix "-diamond-marker") :viewBox "0 0 6 6" :refX "3" :refY "3" :markerWidth "6" :markerHeight "6" :orient "auto-start-reverse" - :fill stroke-color - :fillOpacity stroke-opacity} + :fill color + :fillOpacity opacity} [:path {:d "M 3 0 L 6 3 L 3 6 L 0 3 z"}]]) ;; If the user wants line caps but different in each end, ;; simulate it with markers. - (when (and (or (= cap-start :round) (= cap-end :round)) + (when (and (or (= cap-start :round) + (= cap-end :round)) (not= cap-start cap-end)) - [:marker {:id (str marker-id-prefix "-round") + [:marker {:id (dm/str id-prefix "-round") :viewBox "0 0 6 6" :refX "3" :refY "3" :markerWidth "6" :markerHeight "6" :orient "auto-start-reverse" - :fill stroke-color - :fillOpacity stroke-opacity} + :fill color + :fillOpacity opacity} [:path {:d "M 3 2.5 A 0.5 0.5 0 0 1 3 3.5 "}]]) - (when (and (or (= cap-start :square) (= cap-end :square)) + (when (and (or (= cap-start :square) + (= cap-end :square)) (not= cap-start cap-end)) - [:marker {:id (str marker-id-prefix "-square") + [:marker {:id (dm/str id-prefix "-square") :viewBox "0 0 6 6" :refX "3" :refY "3" :markerWidth "6" :markerHeight "6" :orient "auto-start-reverse" - :fill stroke-color - :fillOpacity stroke-opacity} + :fill color + :fillOpacity opacity} [:rect {:x 3 :y 2.5 :width 0.5 :height 1}]])])) (mf/defc stroke-defs + {::mf/wrap-props false} [{:keys [shape stroke render-id index]}] + (let [open-path? (and ^boolean (cph/path-shape? shape) + ^boolean (gsh/open-path? shape)) + gradient (:stroke-color-gradient stroke) + alignment (:stroke-alignment stroke :center) + width (:stroke-width stroke 0) - (let [open-path? (and (= :path (:type shape)) (gsh/open-path? shape))] + props #js {:id (dm/str "stroke-color-gradient_" render-id "_" index) + :gradient gradient + :shape shape}] [:* - (cond (some? (:stroke-color-gradient stroke)) - (case (:type (:stroke-color-gradient stroke)) - :linear [:> grad/linear-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index) - :gradient (:stroke-color-gradient stroke) - :shape shape}] - :radial [:> grad/radial-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index) - :gradient (:stroke-color-gradient stroke) - :shape shape}])) + (when (some? gradient) + (case (:type gradient) + :linear [:> grad/linear-gradient props] + :radial [:> grad/radial-gradient props])) + (cond (and (not open-path?) - (= :inner (:stroke-alignment stroke :center)) - (> (:stroke-width stroke 0) 0)) + (= :inner alignment) + (> width 0)) [:& inner-stroke-clip-path {:shape shape :render-id render-id :index index}] (and (not open-path?) - (= :outer (:stroke-alignment stroke :center)) - (> (:stroke-width stroke 0) 0)) + (= :outer alignment) + (> width 0)) [:& outer-stroke-mask {:shape shape :stroke stroke :render-id render-id @@ -213,119 +229,140 @@ :render-id render-id :index index}])])) -;; Outer alignment: display the shape in two layers. One -;; without stroke (only fill), and another one only with stroke -;; at double width (transparent fill) and passed through a mask -;; that shows the whole shape, but hides the original shape -;; without stroke +;; Outer alignment: display the shape in two layers. One without +;; stroke (only fill), and another one only with stroke at double +;; width (transparent fill) and passed through a mask that shows the +;; whole shape, but hides the original shape without stroke + (mf/defc outer-stroke {::mf/wrap-props false} [props] - (let [render-id (mf/use-ctx muc/render-id) - child (obj/get props "children") - base-props (obj/get child "props") - elem-name (obj/get child "type") - shape (obj/get props "shape") - stroke (obj/get props "stroke") - index (obj/get props "index") - stroke-width (:stroke-width stroke) + (let [child (unchecked-get props "children") + shape (unchecked-get props "shape") + stroke (unchecked-get props "stroke") + index (unchecked-get props "index") - suffix (if index (str "-" index) "") - stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix) - shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix)] + shape-id (dm/get-prop shape :id) + render-id (mf/use-ctx muc/render-id) + + props (obj/get child "props") + style (obj/get props "style") + + stroke-width (:stroke-width stroke 0) + + suffix (if (some? index) (dm/str "-" index) "") + mask-id (dm/str "outer-stroke-" render-id "-" shape-id suffix) + shape-id (dm/str "stroke-shape-" render-id "-" shape-id suffix) + href (dm/str "#" shape-id)] [:g.outer-stroke-shape [:defs [:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}] - [:> elem-name (-> (obj/clone base-props) - (obj/set! "id" shape-id) - (obj/set! - "style" - (-> (obj/get base-props "style") - (obj/clone) - (obj/without ["fill" "fillOpacity" "stroke" "strokeWidth" "strokeOpacity" "strokeStyle" "strokeDasharray"]))))]] + (let [type (obj/get child "type") + style (-> (obj/clone style) + (obj/unset! "fill") + (obj/unset! "fillOpacity") + (obj/unset! "stroke") + (obj/unset! "strokeWidth") + (obj/unset! "strokeOpacity") + (obj/unset! "strokeStyle") + (obj/unset! "strokeDasharray")) + props (-> (obj/clone props) + (obj/set! "id" shape-id) + (obj/set! "style" style))] - [:use {:href (str "#" shape-id) - :mask (str "url(#" stroke-mask-id ")") - :style (-> (obj/get base-props "style") - (obj/clone) + [:> type props])] + + [:use {:href href + :mask (dm/str "url(#" mask-id ")") + :style (-> (obj/clone style) (obj/set! "strokeWidth" (* stroke-width 2)) - (obj/without ["fill" "fillOpacity"]) - (obj/set! "fill" "none"))}] + (obj/set! "fill" "none") + (obj/unset! "fillOpacity"))}] - [:use {:href (str "#" shape-id) - :style (-> (obj/get base-props "style") - (obj/clone) + [:use {:href href + :style (-> (obj/clone style) (obj/set! "stroke" "none"))}]])) -;; Inner alignment: display the shape with double width stroke, -;; and clip the result with the original shape without stroke. +;; Inner alignment: display the shape with double width stroke, and +;; clip the result with the original shape without stroke. + (mf/defc inner-stroke {::mf/wrap-props false} [props] - (let [render-id (mf/use-ctx muc/render-id) - child (obj/get props "children") - base-props (obj/get child "props") - elem-name (obj/get child "type") - shape (obj/get props "shape") - stroke (obj/get props "stroke") - index (obj/get props "index") - transform (obj/get base-props "transform") + (let [child (unchecked-get props "children") + shape (unchecked-get props "shape") + stroke (unchecked-get props "stroke") + index (unchecked-get props "index") + + shape-id (dm/get-prop shape :id) + render-id (mf/use-ctx muc/render-id) + + type (obj/get child "type") + + props (-> (obj/get child "props") obj/clone) + ;; FIXME: check if style need to be cloned + style (-> (obj/get props "style") obj/clone) + transform (obj/get props "transform") stroke-width (:stroke-width stroke 0) - suffix (if index (str "-" index) "") - clip-id (str "inner-stroke-" render-id "-" (:id shape) suffix) - shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix) + suffix (if (some? index) (dm/str "-" index) "") + clip-id (dm/str "inner-stroke-" render-id "-" shape-id suffix) + shape-id (dm/str "stroke-shape-" render-id "-" shape-id suffix) + clip-path (dm/str "url('#" clip-id "')") - clip-path (str "url('#" clip-id "')") - shape-props (-> base-props - (add-props {:id shape-id - :transform nil}) - (add-style {:strokeWidth (* stroke-width 2)}))] + style (obj/set! style "strokeWidth" (* stroke-width 2)) - [:g.inner-stroke-shape {:transform transform} + props (-> props + (obj/set! "id" (dm/str shape-id)) + (obj/set! "style" style) + (obj/unset! "transform"))] + + [:g.inner-stroke-shape + {:transform transform} [:defs [:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}] - [:> elem-name shape-props]] + [:> type props]] - [:use {:href (str "#" shape-id) + [:use {:href (dm/str "#" shape-id) :clipPath clip-path}]])) -; The SVG standard does not implement yet the 'stroke-alignment' -; attribute, to define the position of the stroke relative to the -; stroke axis (inner, center, outer). Here we implement a patch to be -; able to draw the stroke in the three cases. See discussion at: -; https://stackoverflow.com/questions/7241393/can-you-control-how-an-svgs-stroke-width-is-drawn +;; The SVG standard does not implement yet the 'stroke-alignment' +;; attribute, to define the position of the stroke relative to the +;; stroke axis (inner, center, outer). Here we implement a patch to be +;; able to draw the stroke in the three cases. See discussion at: +;; https://stackoverflow.com/questions/7241393/can-you-control-how-an-svgs-stroke-width-is-drawn + (mf/defc shape-custom-stroke {::mf/wrap-props false} [props] + (let [child (unchecked-get props "children") + shape (unchecked-get props "shape") + stroke (unchecked-get props "stroke") + index (unchecked-get props "index") - (let [child (obj/get props "children") - shape (obj/get props "shape") - stroke (obj/get props "stroke") + render-id (mf/use-ctx muc/render-id) - render-id (mf/use-ctx muc/render-id) - index (obj/get props "index") - stroke-width (:stroke-width stroke 0) - stroke-style (:stroke-style stroke :none) + stroke-width (:stroke-width stroke 0) + stroke-style (:stroke-style stroke :none) stroke-position (:stroke-alignment stroke :center) - has-stroke? (and (> stroke-width 0) - (not= stroke-style :none)) - closed? (or (not= :path (:type shape)) (not (gsh/open-path? shape))) - inner? (= :inner stroke-position) - outer? (= :outer stroke-position)] + + has-stroke? (and (> stroke-width 0) + (not= stroke-style :none)) + closed? (or (not ^boolean (cph/path-shape? shape)) + (not ^boolean (gsh/open-path? shape))) + inner? (= :inner stroke-position) + outer? (= :outer stroke-position)] (cond (and has-stroke? inner? closed?) - [:& inner-stroke {:shape shape :stroke stroke :index index} - child] + [:& inner-stroke {:shape shape :stroke stroke :index index} child] (and has-stroke? outer? closed?) - [:& outer-stroke {:shape shape :stroke stroke :index index} - child] + [:& outer-stroke {:shape shape :stroke stroke :index index} child] :else [:g.stroke-shape @@ -333,145 +370,156 @@ [:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]] child]))) -(defn build-fill-props [shape child position render-id] - (let [url-fill? (or (some? (:fill-image shape)) - (= :image (:type shape)) - (> (count (:fills shape)) 1) - (some :fill-color-gradient (:fills shape))) +(defn- build-fill-element + [shape child position render-id] + (let [shape-fills (get shape :fills) + shape-shadow (get shape :shadow) + shape-blur (get shape :blur) - props (cond-> (obj/create) - (or - ;; There are any shadows - (and (seq (->> (:shadow shape) (remove :hidden))) (not (cph/frame-shape? shape))) - ;; There is a blur - (and (:blur shape) (-> shape :blur :hidden not) (not (cph/frame-shape? shape)))) - (obj/set! "filter" (dm/fmt "url(#filter_%)" render-id))) + type (obj/get child "type") + props (-> (obj/get child "props") + (obj/clone)) + style (-> (obj/get props "style") + (obj/clone)) - svg-defs (:svg-defs shape {}) - svg-attrs (:svg-attrs shape {}) + url-fill? (or ^boolean (some? (:fill-image shape)) + ^boolean (cph/image-shape? shape) + ^boolean (> (count shape-fills) 1) + ^boolean (some? (some :fill-color-gradient shape-fills))) - [svg-attrs svg-styles] - (attrs/extract-svg-attrs render-id svg-defs svg-attrs)] + props (if (cph/frame-shape? shape) + props + (if (or (some? (->> shape-shadow (remove :hidden) seq)) + (not ^boolean (:hidden shape-blur))) + (obj/set! props "filter" (dm/fmt "url(#filter_%)" render-id)) + props)) + + svg-attrs (attrs/get-svg-attrs shape render-id) + svg-styles (get svg-attrs :style {})] (cond - url-fill? - (let [props (obj/set! props - "style" - (-> (obj/get child "props") - (obj/get "style") - (obj/clone) - (obj/without ["fill" "fillOpacity"])))] + ^boolean url-fill? + (do + (obj/unset! style "fill") + (obj/unset! style "fillOpacity") (obj/set! props "fill" (dm/fmt "url(#fill-%-%)" position render-id))) - (and (some? svg-styles) (obj/contains? svg-styles "fill")) - (let [style - (-> (obj/get child "props") - (obj/get "style") - (obj/clone) - (obj/set! "fill" (obj/get svg-styles "fill")) - (obj/set! "fillOpacity" (obj/get svg-styles "fillOpacity")))] - (-> props - (obj/set! "style" style))) + (and ^boolean (or (contains? svg-styles :fill) + (contains? svg-styles :fillOpacity)) + ^boolean (obj/contains? svg-styles "fill")) - (and (some? svg-attrs) (empty? (:fills shape))) - (let [style - (-> (obj/get child "props") - (obj/get "style") - (obj/clone)) + (let [fill (get svg-styles :fill) + opacity (get svg-styles :fillOpacity)] + (when (some? fill) + (obj/set! style "fill" fill)) + (when (some? opacity) + (obj/set! style "fillOpacity" opacity))) - style (-> style - (obj/set! "fill" (obj/get svg-attrs "fill")) - (obj/set! "fillOpacity" (obj/get svg-attrs "fillOpacity")))] - (-> props - (obj/set! "style" style))) + (and ^boolean (or (contains? svg-attrs :fill) + (contains? svg-attrs :fillOpacity)) + ^boolean (empty? shape-fills)) + (let [fill (get svg-attrs :fill) + opacity (get svg-attrs :fillOpacity)] + (when (some? fill) + (obj/set! style "fill" fill)) + (when (some? opacity) + (obj/set! style "fillOpacity" opacity))) - (d/not-empty? (:fills shape)) - (let [fill-props - (attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0 (:type shape)) + ^boolean (d/not-empty? shape-fills) + (let [fill (nth shape-fills 0)] + (obj/merge! style (attrs/get-fill-style fill render-id 0 (dm/get-prop shape :type)))) - style (-> (obj/get child "props") - (obj/get "style") - (obj/clone) - (obj/merge! (obj/get fill-props "style")))] + (and ^boolean (cph/path-shape? shape) + ^boolean (empty? shape-fills)) + (obj/set! style "fill" "none")) - (cond-> (obj/merge! props fill-props) - (some? style) - (obj/set! "style" style))) + (let [props (obj/set! props "style" style)] + (mf/html [:> type props])))) - (and (= :path (:type shape)) (empty? (:fills shape))) - (let [style - (-> (obj/get child "props") - (obj/get "style") - (obj/clone) - (obj/set! "fill" "none"))] - (-> props - (obj/set! "style" style))) +(defn- build-stroke-element + [child value position render-id] + (let [props (obj/get child "props") + type (obj/get child "type") - :else - (obj/create)))) - -(defn build-stroke-props [position child value render-id] - (let [props (-> (obj/get child "props") + style (-> (obj/get props "style") (obj/clone) - (obj/without ["fill" "fillOpacity"]))] - (-> props - (obj/set! - "style" - (-> (obj/get props "style") - (obj/set! "fill" "none") - (obj/set! "fillOpacity" "none"))) - (add-style (obj/get (attrs/extract-stroke-attrs value position render-id) "style"))))) + (obj/set! "fill" "none") + (obj/set! "fillOpacity" "none") + (obj/merge! (attrs/get-stroke-style value position render-id))) + + props (-> (obj/clone props) + (obj/unset! "fill") + (obj/unset! "fillOpacity") + (obj/set! "style" style))] + + (mf/html [:> type props]))) (mf/defc shape-fills {::mf/wrap-props false} [props] - (let [child (obj/get props "children") - shape (obj/get props "shape") - elem-name (obj/get child "type") - position (or (obj/get props "position") 0) - render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id)) - fill-props (build-fill-props shape child position render-id)] - [:g.fills {:id (dm/fmt "fills-%" (:id shape))} - [:> elem-name (-> (obj/get child "props") - (obj/clone) - (obj/merge! fill-props))]])) + (let [child (unchecked-get props "children") + shape (unchecked-get props "shape") + + shape-id (dm/get-prop shape :id) + + position (d/nilv (unchecked-get props "position") 0) + + render-id (mf/use-ctx muc/render-id) + render-id (d/nilv (unchecked-get props "render-id") render-id)] + + [:g.fills {:id (dm/fmt "fills-%" shape-id)} + (build-fill-element shape child position render-id)])) (mf/defc shape-strokes {::mf/wrap-props false} [props] - (let [child (obj/get props "children") - shape (obj/get props "shape") + (let [child (unchecked-get props "children") + shape (unchecked-get props "shape") - elem-name (obj/get child "type") - render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id)) - stroke-id (dm/fmt "strokes-%" (:id shape)) - stroke-props (-> (obj/create) - (obj/set! "id" stroke-id) - (obj/set! "className" "strokes") - (cond-> - ;; There is a blur - (and (:blur shape) (not (cph/frame-shape? shape)) (-> shape :blur :hidden not)) - (obj/set! "filter" (dm/fmt "url(#filter_blur_%)" render-id)) + shape-id (dm/get-prop shape :id) - ;; There are any shadows and no fills - (and (empty? (:fills shape)) (not (cph/frame-shape? shape)) (seq (->> (:shadow shape) (remove :hidden)))) - (obj/set! "filter" (dm/fmt "url(#filter_%)" render-id))))] - [:* - (when - (d/not-empty? (:strokes shape)) - [:> :g stroke-props - (for [[index value] (-> (d/enumerate (:strokes shape)) reverse)] - (let [props (build-stroke-props index child value render-id)] - [:& shape-custom-stroke {:shape shape :stroke value :index index :key (dm/str index "-" stroke-id)} - [:> elem-name props]]))])])) + render-id (mf/use-ctx muc/render-id) + render-id (d/nilv (unchecked-get props "render-id") render-id) + + stroke-id (dm/fmt "strokes-%" shape-id) + + shape-blur (get shape :blur) + shape-fills (get shape :fills) + shape-shadow (get shape :shadow) + shape-strokes (get shape :strokes) + + props #js {:id stroke-id :className "strokes"} + props (if ^boolean (cph/frame-shape? shape) + props + (cond + (and (some? shape-blur) + (not ^boolean (:hidden shape-blur))) + (obj/set! props "filter" (dm/fmt "url(#filter_blur_%)" render-id)) + + (and (empty? shape-fills) + (some? (->> shape-shadow (remove :hidden) seq))) + (obj/set! props "filter" (dm/fmt "url(#filter_%)" render-id))))] + + + (when (d/not-empty? shape-strokes) + [:> :g props + (for [[index value] (-> (d/enumerate shape-strokes) reverse)] + [:& shape-custom-stroke {:shape shape + :stroke value + :index index + :key (dm/str index "-" stroke-id)} + (build-stroke-element child value index render-id)])]))) (mf/defc shape-custom-strokes {::mf/wrap-props false} [props] - (let [children (obj/get props "children") - shape (obj/get props "shape") - position (obj/get props "position") - render-id (obj/get props "render-id")] + (let [children (unchecked-get props "children") + shape (unchecked-get props "shape") + position (unchecked-get props "position") + render-id (unchecked-get props "render-id") + props #js {:shape shape + :position position + :render-id render-id}] [:* - [:& shape-fills {:shape shape :position position :render-id render-id} children] - [:& shape-strokes {:shape shape :position position :render-id render-id} children]])) + [:> shape-fills props children] + [:> shape-strokes props children]])) diff --git a/frontend/src/app/main/ui/shapes/fills.cljs b/frontend/src/app/main/ui/shapes/fills.cljs index de37f1560..9ba40a95a 100644 --- a/frontend/src/app/main/ui/shapes/fills.cljs +++ b/frontend/src/app/main/ui/shapes/fills.cljs @@ -62,13 +62,14 @@ [:* {:key (dm/str shape-index)} (for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)] (when (some? (:fill-color-gradient value)) - (let [props #js {:id (dm/str "fill-color-gradient_" render-id "_" fill-index) + (let [gradient (:fill-color-gradient value) + props #js {:id (dm/str "fill-color-gradient_" render-id "_" fill-index) :key (dm/str fill-index) - :gradient (:fill-color-gradient value) + :gradient gradient :shape shape}] - (case (d/name (:type (:fill-color-gradient value))) - "linear" [:> grad/linear-gradient props] - "radial" [:> grad/radial-gradient props])))) + (case (:type gradient) + :linear [:> grad/linear-gradient props] + :radial [:> grad/radial-gradient props])))) (let [fill-id (dm/str "fill-" shape-index "-" render-id)] @@ -79,10 +80,12 @@ (obj/set! "height" (* height no-repeat-padding))))) [:g (for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)] - [:> :rect (-> (attrs/extract-fill-attrs value render-id fill-index type) - (obj/set! "key" (dm/str fill-index)) - (obj/set! "width" width) - (obj/set! "height" height))]) + (let [style (attrs/get-fill-style value fill-index render-id type) + props #js {:key (dm/str fill-index) + :width width + :height height + :style style}] + [:> :rect props])) (when has-image? [:g diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 45a3bc0fe..659ce9f23 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -11,6 +11,7 @@ [app.common.pages.helpers :as cph] [app.main.refs :as refs] [app.main.ui.context :as muc] + [app.main.ui.hooks :as h] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.fills :as fills] @@ -63,7 +64,7 @@ (or (:blend-mode shape))) type (dm/get-prop shape :type) - render-id (mf/use-id) + render-id (h/use-render-id) filter-id (dm/str "filter_" render-id) styles (-> (obj/create) (obj/set! "pointerEvents" pointer-events) diff --git a/frontend/src/app/util/object.cljs b/frontend/src/app/util/object.cljs index e972a2396..4c6d31981 100644 --- a/frontend/src/app/util/object.cljs +++ b/frontend/src/app/util/object.cljs @@ -99,3 +99,13 @@ (defn ^boolean in? [obj prop] (js* "~{} in ~{}" prop obj)) + +(defn map->obj + [o] + (reduce-kv (fn [result k v] + (let [k (if (keyword? k) (name k) k) + v (if (keyword? v) (name v) v)] + (unchecked-set result k v) + result)) + #js {} + o))