mirror of
https://github.com/penpot/penpot.git
synced 2025-01-25 07:58:49 -05:00
commit
d7dea040af
17 changed files with 399 additions and 339 deletions
|
@ -18,6 +18,7 @@
|
|||
app.util.services/defmethod hooks.export/service-defmethod
|
||||
app.common.record/defrecord hooks.export/penpot-defrecord
|
||||
app.db/with-atomic hooks.export/penpot-with-atomic
|
||||
rumext.v2/fnc hooks.export/rumext-fnc
|
||||
}}
|
||||
|
||||
:output
|
||||
|
|
|
@ -58,6 +58,19 @@
|
|||
(api/vector-node [params params])]
|
||||
body))})))
|
||||
|
||||
(defn rumext-fnc
|
||||
[{:keys [node]}]
|
||||
(let [[cname mdata params & body] (rest (:children node))
|
||||
[params body] (if (api/vector-node? mdata)
|
||||
[mdata (cons params body)]
|
||||
[params body])]
|
||||
(let [result (api/list-node
|
||||
(into [(api/token-node 'fn)
|
||||
params]
|
||||
(cons mdata body)))]
|
||||
{:node result})))
|
||||
|
||||
|
||||
(defn penpot-defrecord
|
||||
[{:keys [:node]}]
|
||||
(let [[rnode rtype rparams & other] (:children node)
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
ffeat/*wrap-with-pointer-map-fn*
|
||||
(if (contains? (:features file) "storage/pointer-map") pmap/wrap identity)
|
||||
ffeat/*wrap-with-objects-map-fn*
|
||||
(if (contains? (:features file) "storage/objectd-map") omap/wrap identity)]
|
||||
(if (contains? (:features file) "storage/objects-map") omap/wrap identity)]
|
||||
(try
|
||||
(on-file file)
|
||||
(catch Throwable cause
|
||||
|
|
|
@ -538,8 +538,9 @@
|
|||
;; Implemented with transients for performance. 30~50% better
|
||||
(letfn [(process-shape [objects [id shape]]
|
||||
(let [frame-id (if (= :frame (:type shape)) id (:frame-id shape))
|
||||
cur (-> (or (get objects frame-id) (transient {}))
|
||||
(assoc! id shape))]
|
||||
cur (-> (or (get objects frame-id)
|
||||
(transient {}))
|
||||
(assoc! id shape))]
|
||||
(assoc! objects frame-id cur)))]
|
||||
(update-vals
|
||||
(->> objects
|
||||
|
|
|
@ -311,8 +311,8 @@
|
|||
[id]
|
||||
(l/derived
|
||||
(fn [objects]
|
||||
(let [children-ids (get-in objects [id :shapes])]
|
||||
(into [] (keep (d/getf objects)) children-ids)))
|
||||
(->> (dm/get-in objects [id :shapes])
|
||||
(into [] (keep (d/getf objects)))))
|
||||
workspace-page-objects =))
|
||||
|
||||
(defn all-children-objects
|
||||
|
|
|
@ -8,39 +8,41 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.hooks :refer [use-equal-memo]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.shapes.export :as use]
|
||||
[app.main.ui.shapes.path :refer [path-shape]]
|
||||
[app.util.object :as obj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn bool-shape
|
||||
[shape-wrapper]
|
||||
(mf/fnc bool-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (obj/get props "shape")
|
||||
childs (obj/get props "childs")
|
||||
childs (use-equal-memo childs)
|
||||
include-metadata? (mf/use-ctx use/include-metadata-ctx)
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
children (unchecked-get props "childs")
|
||||
children (h/use-equal-memo children)
|
||||
|
||||
bool-content
|
||||
(mf/use-memo
|
||||
(mf/deps shape childs)
|
||||
(fn []
|
||||
(cond
|
||||
(some? (:bool-content shape))
|
||||
(:bool-content shape)
|
||||
metadata? (mf/use-ctx use/include-metadata-ctx)
|
||||
content (mf/with-memo [shape children]
|
||||
(let [content (:bool-content shape)]
|
||||
(cond
|
||||
(some? content)
|
||||
content
|
||||
|
||||
(some? childs)
|
||||
(gsh/calc-bool-content shape childs))))]
|
||||
(some? children)
|
||||
(gsh/calc-bool-content shape children))))
|
||||
|
||||
[:*
|
||||
(when (some? bool-content)
|
||||
[:& path-shape {:shape (assoc shape :content bool-content)}])
|
||||
shape (mf/with-memo [shape content]
|
||||
(assoc shape :content content))]
|
||||
|
||||
(when include-metadata?
|
||||
[:> "penpot:bool" {}
|
||||
(for [item (->> (:shapes shape) (mapv #(get childs %)))]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (dm/str (:id item))}])])])))
|
||||
[:*
|
||||
(when (some? content)
|
||||
[:& path-shape {:shape shape}])
|
||||
|
||||
(when metadata?
|
||||
;; FIXME: get children looks wrong
|
||||
[:> "penpot:bool" {}
|
||||
(for [item (map #(get children %) (:shapes shape))]
|
||||
[:& shape-wrapper
|
||||
{:shape item
|
||||
:key (dm/str (dm/get-prop item :id))}])])])))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui.shapes.circle
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.custom-stroke :refer [shape-custom-strokes]]
|
||||
|
@ -16,21 +17,22 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [x y width height]} shape
|
||||
transform (gsh/transform-str shape)
|
||||
|
||||
cx (+ x (/ width 2))
|
||||
cy (+ y (/ height 2))
|
||||
rx (/ width 2)
|
||||
ry (/ height 2)
|
||||
x (dm/get-prop shape :x)
|
||||
y (dm/get-prop shape :y)
|
||||
w (dm/get-prop shape :width)
|
||||
h (dm/get-prop shape :height)
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:cx cx
|
||||
:cy cy
|
||||
:rx rx
|
||||
:ry ry
|
||||
:transform transform}))]
|
||||
t (gsh/transform-str shape)
|
||||
|
||||
cx (+ x (/ w 2))
|
||||
cy (+ y (/ h 2))
|
||||
rx (/ w 2)
|
||||
ry (/ h 2)
|
||||
|
||||
props (mf/with-memo [shape]
|
||||
(-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge! #js {:cx cx :cy cy :rx rx :ry ry :transform t})))]
|
||||
|
||||
[:& shape-custom-strokes {:shape shape}
|
||||
[:> :ellipse props]]))
|
||||
|
|
|
@ -20,32 +20,39 @@
|
|||
[debug :refer [debug?]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn frame-clip-id
|
||||
(defn- frame-clip-id
|
||||
[shape render-id]
|
||||
(dm/str "frame-clip-" (:id shape) "-" render-id))
|
||||
(dm/str "frame-clip-" (dm/get-prop shape :id) "-" render-id))
|
||||
|
||||
(defn frame-clip-url
|
||||
(defn- frame-clip-url
|
||||
[shape render-id]
|
||||
(when (= :frame (:type shape))
|
||||
(dm/str "url(#" (frame-clip-id shape render-id) ")")))
|
||||
(dm/str "url(#" (frame-clip-id shape render-id) ")"))
|
||||
|
||||
(mf/defc frame-clip-def
|
||||
[{:keys [shape render-id]}]
|
||||
(when (and (= :frame (:type shape)) (not (:show-content shape)))
|
||||
(let [{:keys [x y width height]} shape
|
||||
transform (gsh/transform-str shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:transform transform}))
|
||||
path? (some? (.-d props))]
|
||||
[:clipPath.frame-clip-def {:id (frame-clip-id shape render-id) :class "frame-clip"}
|
||||
(if ^boolean path?
|
||||
[:> :path props]
|
||||
[:> :rect props])])))
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")]
|
||||
(when (and ^boolean (cph/frame-shape? shape)
|
||||
(not ^boolean (:show-content shape)))
|
||||
|
||||
(let [render-id (unchecked-get props "render-id")
|
||||
x (dm/get-prop shape :x)
|
||||
y (dm/get-prop shape :y)
|
||||
w (dm/get-prop shape :width)
|
||||
h (dm/get-prop shape :height)
|
||||
t (gsh/transform-str shape)
|
||||
|
||||
props (mf/with-memo [shape]
|
||||
(-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge! #js {:x x :y y :width w :height h :transform t})))
|
||||
|
||||
path? (some? (.-d props))]
|
||||
|
||||
[:clipPath {:id (frame-clip-id shape render-id)
|
||||
:class "frame-clip frame-clip-def"}
|
||||
(if ^boolean path?
|
||||
[:> :path props]
|
||||
[:> :rect props])]))))
|
||||
|
||||
;; Wrapper around the frame that will handle things such as strokes and other properties
|
||||
;; we wrap the proper frames and also the thumbnails
|
||||
|
@ -53,29 +60,38 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [shape (unchecked-get props "shape")
|
||||
children (unchecked-get props "children")
|
||||
(let [shape (unchecked-get props "shape")
|
||||
children (unchecked-get props "children")
|
||||
|
||||
{:keys [x y width height show-content]} shape
|
||||
transform (gsh/transform-str shape)
|
||||
render-id (mf/use-ctx muc/render-id)
|
||||
|
||||
render-id (mf/use-ctx muc/render-id)
|
||||
x (dm/get-prop shape :x)
|
||||
y (dm/get-prop shape :y)
|
||||
w (dm/get-prop shape :width)
|
||||
h (dm/get-prop shape :height)
|
||||
transform (gsh/transform-str shape)
|
||||
|
||||
show-content? (get shape :show-content)
|
||||
|
||||
props (mf/with-memo [shape render-id]
|
||||
(-> (attrs/extract-style-attrs shape render-id)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:width w
|
||||
:height h
|
||||
:transform transform
|
||||
:className "frame-background"})))
|
||||
path? (some? (.-d props))]
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape render-id)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:transform transform
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))
|
||||
path? (some? (.-d props))]
|
||||
[:*
|
||||
[:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))
|
||||
:fill "none"} ;; A frame sets back normal fill behavior (default transparent). It may have
|
||||
;; been changed to default black if a shape coming from an imported SVG file
|
||||
;; is rendered. See main.ui.shapes.attrs/add-style-attrs.
|
||||
[:& frame-clip-def {:shape shape :render-id render-id}]
|
||||
[:g {:clip-path (when-not ^boolean show-content?
|
||||
(frame-clip-url shape render-id))
|
||||
;; A frame sets back normal fill behavior (default
|
||||
;; transparent). It may have been changed to default black
|
||||
;; if a shape coming from an imported SVG file is
|
||||
;; rendered. See main.ui.shapes.attrs/add-style-attrs.
|
||||
:fill "none"}
|
||||
|
||||
[:& shape-fills {:shape shape}
|
||||
(if ^boolean path?
|
||||
|
@ -94,34 +110,43 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
bounds (or (unchecked-get props "bounds")
|
||||
(grc/points->rect (:points shape)))
|
||||
bounds (unchecked-get props "bounds")
|
||||
|
||||
shape-id (dm/get-prop shape :id)
|
||||
points (dm/get-prop shape :points)
|
||||
|
||||
bounds (mf/with-memo [bounds points]
|
||||
(or bounds (grc/points->rect points)))
|
||||
|
||||
shape-id (:id shape)
|
||||
thumb (:thumbnail shape)
|
||||
|
||||
debug? (debug? :thumbnails)
|
||||
safari? (cf/check-browser? :safari)]
|
||||
safari? (cf/check-browser? :safari)
|
||||
|
||||
;; FIXME: ensure bounds is always a rect instance and
|
||||
;; dm/get-prop for static attr access
|
||||
bx (:x bounds)
|
||||
by (:y bounds)
|
||||
bh (:height bounds)
|
||||
bw (:width bounds)]
|
||||
|
||||
[:*
|
||||
[:image.frame-thumbnail
|
||||
{:id (dm/str "thumbnail-" shape-id)
|
||||
:href thumb
|
||||
:decoding "async"
|
||||
;; FIXME: ensure bounds is always a rect instance and
|
||||
;; dm/get-prop for static attr access
|
||||
:x (:x bounds)
|
||||
:y (:y bounds)
|
||||
:width (:width bounds)
|
||||
:height (:height bounds)
|
||||
:x bx
|
||||
:y by
|
||||
:width bw
|
||||
:height bh
|
||||
:style {:filter (when (and (not ^boolean safari?) ^boolean debug?) "sepia(1)")}}]
|
||||
|
||||
;; Safari don't support filters so instead we add a rectangle around the thumbnail
|
||||
(when (and ^boolean safari? ^boolean debug?)
|
||||
[:rect {:x (+ (:x bounds) 4)
|
||||
:y (+ (:y bounds) 4)
|
||||
:width (- (:width bounds) 8)
|
||||
:height (- (:height bounds) 8)
|
||||
[:rect {:x (+ bx 4)
|
||||
:y (+ by 4)
|
||||
:width (- bw 8)
|
||||
:height (- bh 8)
|
||||
:stroke "red"
|
||||
:stroke-width 2}])]))
|
||||
|
||||
|
@ -138,17 +163,22 @@
|
|||
(mf/fnc frame-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
childs (cond-> childs
|
||||
(ctl/any-layout? shape)
|
||||
(cph/sort-layout-children-z-index))
|
||||
is-component? (mf/use-ctx muc/is-component?)]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
is-component? (mf/use-ctx muc/is-component?)
|
||||
childs (cond-> childs
|
||||
(ctl/any-layout? shape)
|
||||
(cph/sort-layout-children-z-index))
|
||||
]
|
||||
|
||||
[:> frame-container props
|
||||
[:g.frame-children {:opacity (:opacity shape)}
|
||||
(for [{:keys [id] :as item} childs]
|
||||
(when (some? id)
|
||||
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}]))]
|
||||
(when (and is-component? (empty? childs))
|
||||
(for [item childs]
|
||||
(let [id (dm/get-prop item :id)]
|
||||
(when (some? id)
|
||||
[:& shape-wrapper {:key (dm/str id) :shape item}])))]
|
||||
|
||||
(when (and ^boolean is-component?
|
||||
^boolean (empty? childs))
|
||||
[:& grid-layout-viewer {:shape shape :childs childs}])])))
|
||||
|
||||
|
|
|
@ -10,108 +10,130 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.export :as ed]
|
||||
[app.util.object :as obj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn add-metadata [props gradient]
|
||||
(defn- add-metadata!
|
||||
[props gradient]
|
||||
(-> props
|
||||
(obj/set! "penpot:gradient" "true")
|
||||
(obj/set! "penpot:start-x" (:start-x gradient))
|
||||
(obj/set! "penpot:start-x" (:start-x gradient))
|
||||
(obj/set! "penpot:start-y" (:start-y gradient))
|
||||
(obj/set! "penpot:end-x" (:end-x gradient))
|
||||
(obj/set! "penpot:end-y" (:end-y gradient))
|
||||
(obj/set! "penpot:width" (:width gradient))))
|
||||
|
||||
(mf/defc linear-gradient [{:keys [id gradient shape]}]
|
||||
(let [transform (when (= :path (:type shape))
|
||||
(gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))
|
||||
(mf/defc linear-gradient
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [id gradient shape]}]
|
||||
(let [transform (mf/with-memo [shape]
|
||||
(when (cph/frame-shape? shape)
|
||||
(gsh/transform-matrix shape nil (gpt/point 0.5 0.5))))
|
||||
|
||||
base-props #js {:id id
|
||||
:x1 (:start-x gradient)
|
||||
:y1 (:start-y gradient)
|
||||
:x2 (:end-x gradient)
|
||||
:y2 (:end-y gradient)
|
||||
:gradientTransform (dm/str transform)}
|
||||
metadata? (mf/use-ctx ed/include-metadata-ctx)
|
||||
props #js {:id id
|
||||
:x1 (:start-x gradient)
|
||||
:y1 (:start-y gradient)
|
||||
:x2 (:end-x gradient)
|
||||
:y2 (:end-y gradient)
|
||||
:gradientTransform (dm/str transform)}]
|
||||
|
||||
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
|
||||
|
||||
props (cond-> base-props
|
||||
include-metadata?
|
||||
(add-metadata gradient))]
|
||||
(when ^boolean metadata?
|
||||
(add-metadata! props gradient))
|
||||
|
||||
[:> :linearGradient props
|
||||
(for [{:keys [offset color opacity]} (:stops gradient)]
|
||||
[:stop {:key (dm/str id "-stop-" offset)
|
||||
:offset (or offset 0)
|
||||
:offset (d/nilv offset 0)
|
||||
:stop-color color
|
||||
:stop-opacity opacity}])]))
|
||||
|
||||
(mf/defc radial-gradient [{:keys [id gradient shape]}]
|
||||
(let [path? (= :path (:type shape))
|
||||
shape-transform (or (when path? (:transform shape)) (gmt/matrix))
|
||||
shape-transform-inv (or (when path? (:transform-inverse shape)) (gmt/matrix))
|
||||
(mf/defc radial-gradient
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [id gradient shape]}]
|
||||
(let [path? (cph/path-shape? shape)
|
||||
|
||||
transform (when ^boolean path?
|
||||
(dm/get-prop shape :transform))
|
||||
transform (d/nilv transform gmt/base)
|
||||
|
||||
transform-inv (when ^boolean path?
|
||||
(dm/get-prop shape :transform-inverse))
|
||||
transform-inv (d/nilv transform-inv gmt/base)
|
||||
|
||||
{:keys [start-x start-y end-x end-y] gwidth :width} gradient
|
||||
|
||||
gradient-vec (gpt/to-vec (gpt/point start-x start-y)
|
||||
(gpt/point end-x end-y))
|
||||
gstart-pt (gpt/point start-x start-y)
|
||||
gend-pt (gpt/point end-x end-y)
|
||||
gradient-vec (gpt/to-vec gstart-pt gend-pt)
|
||||
|
||||
angle (+ (gpt/angle gradient-vec) 90)
|
||||
angle (+ (gpt/angle gradient-vec) 90)
|
||||
|
||||
bb-shape (gsh/shapes->rect [shape])
|
||||
points (dm/get-prop shape :points)
|
||||
bounds (mf/with-memo [points]
|
||||
(grc/points->rect points))
|
||||
selrect (dm/get-prop shape :selrect)
|
||||
|
||||
;; Paths don't have a transform in SVG because we transform the points
|
||||
;; we need to compensate the difference between the original rectangle
|
||||
;; and the transformed one. This factor is that calculation.
|
||||
factor (if path?
|
||||
(/ (:height (:selrect shape)) (:height bb-shape))
|
||||
1.0)
|
||||
;; Paths don't have a transform in SVG because we transform
|
||||
;; the points we need to compensate the difference between the
|
||||
;; original rectangle and the transformed one. This factor is
|
||||
;; that calculation.
|
||||
factor (if ^boolean path?
|
||||
(/ (dm/get-prop selrect :height)
|
||||
(dm/get-prop bounds :height))
|
||||
1.0)
|
||||
|
||||
transform (-> (gmt/matrix)
|
||||
(gmt/translate (gpt/point start-x start-y))
|
||||
(gmt/multiply shape-transform)
|
||||
(gmt/rotate angle)
|
||||
(gmt/scale (gpt/point gwidth factor))
|
||||
(gmt/multiply shape-transform-inv)
|
||||
(gmt/translate (gpt/negate (gpt/point start-x start-y))))
|
||||
transform (mf/with-memo [gradient transform transform-inv factor]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate gstart-pt)
|
||||
(gmt/multiply transform)
|
||||
(gmt/rotate angle)
|
||||
(gmt/scale (gpt/point gwidth factor))
|
||||
(gmt/multiply transform-inv)
|
||||
(gmt/translate (gpt/negate gstart-pt))))
|
||||
|
||||
gradient-radius (gpt/length gradient-vec)
|
||||
base-props #js {:id id
|
||||
:cx start-x
|
||||
:cy start-y
|
||||
:r gradient-radius
|
||||
:gradientTransform transform}
|
||||
metadata? (mf/use-ctx ed/include-metadata-ctx)
|
||||
|
||||
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
|
||||
props #js {:id id
|
||||
:cx start-x
|
||||
:cy start-y
|
||||
:r (gpt/length gradient-vec)
|
||||
:gradientTransform transform}]
|
||||
|
||||
(when ^boolean metadata?
|
||||
(add-metadata! props gradient))
|
||||
|
||||
props (cond-> base-props
|
||||
include-metadata?
|
||||
(add-metadata gradient))]
|
||||
[:> :radialGradient props
|
||||
(for [{:keys [offset color opacity]} (:stops gradient)]
|
||||
[:stop {:key (dm/str id "-stop-" offset)
|
||||
:offset (or offset 0)
|
||||
:offset (d/nilv offset 0)
|
||||
:stop-color color
|
||||
:stop-opacity opacity}])]))
|
||||
|
||||
(mf/defc gradient
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [attr (obj/get props "attr")
|
||||
shape (obj/get props "shape")
|
||||
id (obj/get props "id")
|
||||
id' (mf/use-ctx muc/render-id)
|
||||
id (or id (dm/str (name attr) "_" id'))
|
||||
(let [attr (unchecked-get props "attr")
|
||||
shape (unchecked-get props "shape")
|
||||
id (unchecked-get props "id")
|
||||
rid (mf/use-ctx muc/render-id)
|
||||
|
||||
id (if (some? id)
|
||||
id
|
||||
(dm/str (name attr) "_" rid))
|
||||
|
||||
gradient (get shape attr)
|
||||
gradient-props #js {:id id
|
||||
:gradient gradient
|
||||
:shape shape}]
|
||||
(when gradient
|
||||
(case (d/name (:type gradient))
|
||||
"linear" [:> linear-gradient gradient-props]
|
||||
"radial" [:> radial-gradient gradient-props]
|
||||
props #js {:id id
|
||||
:gradient gradient
|
||||
:shape shape}]
|
||||
|
||||
(when (some? gradient)
|
||||
(case (:type gradient)
|
||||
:linear [:> linear-gradient props]
|
||||
:radial [:> radial-gradient props]
|
||||
nil))))
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.mask :refer [mask-url clip-url mask-factory]]
|
||||
[app.util.object :as obj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn group-shape
|
||||
|
@ -18,41 +17,40 @@
|
|||
(mf/fnc group-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
objects (unchecked-get props "objects")
|
||||
render-id (mf/use-ctx muc/render-id)
|
||||
masked-group? (:masked-group shape)
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
render-id (mf/use-ctx muc/render-id)
|
||||
masked-group? (:masked-group shape)
|
||||
|
||||
[mask childs] (if masked-group?
|
||||
[(first childs) (rest childs)]
|
||||
[nil childs])
|
||||
mask (if ^boolean masked-group?
|
||||
(first childs)
|
||||
nil)
|
||||
childs (if ^boolean masked-group?
|
||||
(rest childs)
|
||||
childs)
|
||||
|
||||
;; We need to separate mask and clip into two because a bug in Firefox
|
||||
;; breaks when the group has clip+mask+foreignObject
|
||||
;; Clip and mask separated will work in every platform
|
||||
; Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1734805
|
||||
[clip-wrapper clip-props]
|
||||
(if masked-group?
|
||||
["g" (-> (obj/create)
|
||||
(obj/set! "clipPath" (clip-url render-id mask)))]
|
||||
[mf/Fragment nil])
|
||||
wrapper (if ^boolean masked-group? "g" mf/Fragment)
|
||||
clip-props (if ^boolean masked-group?
|
||||
#js {:clipPath (clip-url render-id mask)}
|
||||
#js {})
|
||||
|
||||
[mask-wrapper mask-props]
|
||||
(if masked-group?
|
||||
["g" (-> (obj/create)
|
||||
(obj/set! "mask" (mask-url render-id mask)))]
|
||||
[mf/Fragment nil])]
|
||||
mask-props (if ^boolean masked-group?
|
||||
#js {:mask (mask-url render-id mask)}
|
||||
#js {})]
|
||||
|
||||
[:> clip-wrapper clip-props
|
||||
[:> mask-wrapper mask-props
|
||||
(when masked-group?
|
||||
[:> render-mask #js {:mask mask
|
||||
:objects objects}])
|
||||
;; We need to separate mask and clip into two because a bug in
|
||||
;; Firefox breaks when the group has clip+mask+foreignObject
|
||||
;; Clip and mask separated will work in every platform Firefox
|
||||
;; bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1734805
|
||||
[:> wrapper clip-props
|
||||
[:> wrapper mask-props
|
||||
(when ^boolean masked-group?
|
||||
[:& render-mask {:mask mask}])
|
||||
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (dm/str (:id item))}])]]))))
|
||||
[:& shape-wrapper
|
||||
{:shape item
|
||||
:key (dm/str (dm/get-prop item :id))}])]]))))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -9,27 +9,28 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.ui.context :as muc]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn mask-id [render-id mask]
|
||||
(str render-id "-" (:id mask) "-mask"))
|
||||
(dm/str render-id "-" (:id mask) "-mask"))
|
||||
|
||||
(defn mask-url [render-id mask]
|
||||
(str "url(#" (mask-id render-id mask) ")"))
|
||||
(dm/str "url(#" (mask-id render-id mask) ")"))
|
||||
|
||||
(defn clip-id [render-id mask]
|
||||
(str render-id "-" (:id mask) "-clip"))
|
||||
(dm/str render-id "-" (:id mask) "-clip"))
|
||||
|
||||
(defn clip-url [render-id mask]
|
||||
(str "url(#" (clip-id render-id mask) ")"))
|
||||
(dm/str "url(#" (clip-id render-id mask) ")"))
|
||||
|
||||
(defn filter-id [render-id mask]
|
||||
(str render-id "-" (:id mask) "-filter"))
|
||||
(dm/str render-id "-" (:id mask) "-filter"))
|
||||
|
||||
(defn filter-url [render-id mask]
|
||||
(str "url(#" (filter-id render-id mask) ")"))
|
||||
(dm/str "url(#" (filter-id render-id mask) ")"))
|
||||
|
||||
(defn set-white-fill
|
||||
[shape]
|
||||
|
@ -42,17 +43,39 @@
|
|||
(d/update-when :position-data #(mapv update-color %))
|
||||
(assoc :stroke-color "#FFFFFF" :stroke-opacity 1))))
|
||||
|
||||
(defn- point->str
|
||||
[point]
|
||||
(dm/str (dm/get-prop point :x) "," (dm/get-prop point :y)))
|
||||
|
||||
(defn mask-factory
|
||||
[shape-wrapper]
|
||||
(mf/fnc mask-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [mask (unchecked-get props "mask")
|
||||
render-id (mf/use-ctx muc/render-id)
|
||||
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
|
||||
(let [mask (unchecked-get props "mask")
|
||||
render-id (mf/use-ctx muc/render-id)
|
||||
|
||||
svg-text? (and ^boolean (cph/text-shape? mask)
|
||||
^boolean (some? (:position-data mask)))
|
||||
|
||||
points (dm/get-prop mask :points)
|
||||
points-str (mf/with-memo [points]
|
||||
(->> (map point->str points)
|
||||
(str/join " ")))
|
||||
|
||||
bounds (mf/with-memo [points]
|
||||
(grc/points->rect points))
|
||||
|
||||
bx (dm/get-prop bounds :x)
|
||||
by (dm/get-prop bounds :y)
|
||||
bw (dm/get-prop bounds :width)
|
||||
bh (dm/get-prop bounds :height)
|
||||
|
||||
shape (mf/with-memo [mask]
|
||||
(-> mask
|
||||
(dissoc :shadow :blur)
|
||||
(assoc :is-mask? true)))]
|
||||
|
||||
mask-bb (:points mask)
|
||||
mask-bb-rect (grc/points->rect mask-bb)]
|
||||
[:defs
|
||||
[:filter {:id (filter-id render-id mask)}
|
||||
[:feFlood {:flood-color "white"
|
||||
|
@ -66,26 +89,26 @@
|
|||
;; we cannot use clips instead of mask because clips can only be simple shapes
|
||||
[:clipPath {:class "mask-clip-path"
|
||||
:id (clip-id render-id mask)}
|
||||
[:polyline {:points (->> mask-bb
|
||||
(map #(dm/str (:x %) "," (:y %)))
|
||||
(str/join " "))}]]
|
||||
[:polyline {:points points-str}]]
|
||||
|
||||
;; When te shape is a text we pass to the shape the info and disable the filter.
|
||||
;; There is a bug in Firefox with filters and texts. We change the text to white at shape level
|
||||
[:mask {:class "mask-shape"
|
||||
:id (mask-id render-id mask)
|
||||
:x (:x mask-bb-rect)
|
||||
:y (:y mask-bb-rect)
|
||||
:width (:width mask-bb-rect)
|
||||
:height (:height mask-bb-rect)
|
||||
:x bx
|
||||
:y by
|
||||
:width bw
|
||||
:height bh
|
||||
|
||||
;; This is necesary to prevent a race condition in the dynamic-modifiers whether the modifier
|
||||
;; triggers afte the render
|
||||
:data-old-x (:x mask-bb-rect)
|
||||
:data-old-y (:y mask-bb-rect)
|
||||
:data-old-width (:width mask-bb-rect)
|
||||
:data-old-height (:height mask-bb-rect)
|
||||
:data-old-x bx
|
||||
:data-old-y by
|
||||
:data-old-width bw
|
||||
:data-old-height bh
|
||||
:mask-units "userSpaceOnUse"}
|
||||
[:g {:filter (when-not svg-text? (filter-url render-id mask))}
|
||||
[:& shape-wrapper {:shape (-> mask (dissoc :shadow :blur) (assoc :is-mask? true))}]]]])))
|
||||
|
||||
[:g {:filter (when-not ^boolean svg-text?
|
||||
(filter-url render-id mask))}
|
||||
[:& shape-wrapper {:shape shape}]]]])))
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
children (unchecked-get props "children")
|
||||
pointer-events (unchecked-get props "pointer-events")
|
||||
disable-shadows? (unchecked-get props "disable-shadows?")
|
||||
shape-id (:id shape)
|
||||
shape-id (dm/get-prop shape :id)
|
||||
|
||||
preview-blend-mode-ref
|
||||
(mf/with-memo [shape-id] (refs/workspace-preview-blend-by-id shape-id))
|
||||
|
@ -62,7 +62,7 @@
|
|||
blend-mode (-> (mf/deref preview-blend-mode-ref)
|
||||
(or (:blend-mode shape)))
|
||||
|
||||
type (:type shape)
|
||||
type (dm/get-prop shape :type)
|
||||
render-id (mf/use-id)
|
||||
filter-id (dm/str "filter_" render-id)
|
||||
styles (-> (obj/create)
|
||||
|
|
|
@ -50,23 +50,14 @@
|
|||
(let [objects (obj/get props "objects")
|
||||
active-frames (obj/get props "active-frames")
|
||||
shapes (cph/get-immediate-children objects)
|
||||
|
||||
;; We group the objects together per frame-id so if an object of a different
|
||||
;; frame changes won't affect the rendering frame
|
||||
frame-objects
|
||||
(mf/with-memo [objects]
|
||||
(cph/objects-by-frame objects))
|
||||
|
||||
|
||||
vbox (mf/use-ctx ctx/current-vbox)
|
||||
|
||||
shapes
|
||||
(mf/with-memo [shapes vbox]
|
||||
(if (some? vbox)
|
||||
(->> shapes
|
||||
(filterv (fn [shape]
|
||||
(grc/overlaps-rects? vbox (dm/get-prop shape :selrect)))))
|
||||
shapes))]
|
||||
shapes (mf/with-memo [shapes vbox]
|
||||
(if (some? vbox)
|
||||
(filter (fn [shape]
|
||||
(grc/overlaps-rects? vbox (dm/get-prop shape :selrect)))
|
||||
shapes)
|
||||
shapes))]
|
||||
|
||||
[:g {:id (dm/str "shape-" uuid/zero)}
|
||||
[:& (mf/provider ctx/active-frames) {:value active-frames}
|
||||
|
@ -83,17 +74,11 @@
|
|||
(if ^boolean (cph/frame-shape? shape)
|
||||
[:& root-frame-wrapper
|
||||
{:shape shape
|
||||
:objects (get frame-objects (dm/get-prop shape :id))
|
||||
:thumbnail? (not (contains? active-frames (dm/get-prop shape :id)))}]
|
||||
[:& shape-wrapper {:shape shape}])])]]]))
|
||||
|
||||
(defn- check-shape-wrapper-props
|
||||
[np op]
|
||||
(frame/check-shape (unchecked-get np "shape")
|
||||
(unchecked-get op "shape")))
|
||||
|
||||
(mf/defc shape-wrapper
|
||||
{::mf/wrap [#(mf/memo' % check-shape-wrapper-props)]
|
||||
{::mf/wrap [#(mf/memo' % common/check-shape-props)]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
|
@ -109,26 +94,27 @@
|
|||
(and (some? active-frames)
|
||||
(not (contains? active-frames shape-id)))
|
||||
|
||||
opts #js {:shape shape :thumbnail? thumbnail?}
|
||||
props #js {:shape shape :thumbnail? thumbnail?}
|
||||
|
||||
[wrapper wrapper-props]
|
||||
(if (= :svg-raw shape-type)
|
||||
[mf/Fragment nil]
|
||||
["g" #js {:className "workspace-shape-wrapper"}])]
|
||||
rawsvg? (= :svg-raw shape-type)
|
||||
wrapper-elem (if ^boolean rawsvg? mf/Fragment "g")
|
||||
wrapper-props (if ^boolean rawsvg?
|
||||
#js {:className "workspace-shape-wrapper"}
|
||||
#js {})]
|
||||
|
||||
(when (and (some? shape)
|
||||
(not ^boolean (:hidden shape)))
|
||||
[:> wrapper wrapper-props
|
||||
[:> wrapper-elem wrapper-props
|
||||
(case shape-type
|
||||
:path [:> path/path-wrapper opts]
|
||||
:text [:> text/text-wrapper opts]
|
||||
:group [:> group-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:svg-raw [:> svg-raw-wrapper opts]
|
||||
:bool [:> bool-wrapper opts]
|
||||
:frame [:> nested-frame-wrapper opts]
|
||||
:path [:> path/path-wrapper props]
|
||||
:text [:> text/text-wrapper props]
|
||||
:group [:> group-wrapper props]
|
||||
:rect [:> rect-wrapper props]
|
||||
:image [:> image-wrapper props]
|
||||
:circle [:> circle-wrapper props]
|
||||
:svg-raw [:> svg-raw-wrapper props]
|
||||
:bool [:> bool-wrapper props]
|
||||
:frame [:> nested-frame-wrapper props]
|
||||
|
||||
nil)])))
|
||||
|
||||
|
|
|
@ -6,47 +6,37 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.bool
|
||||
(:require
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.ui.shapes.bool :as bool]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.util.dom :as dom]
|
||||
[app.main.ui.workspace.shapes.common :refer [check-shape-props]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn use-double-click [{:keys [id]}]
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dw/select-inside-group id @ms/mouse-position)))))
|
||||
|
||||
(defn bool-wrapper-factory
|
||||
[shape-wrapper]
|
||||
(let [shape-component (bool/bool-shape shape-wrapper)]
|
||||
(let [bool-shape (bool/bool-shape shape-wrapper)]
|
||||
(mf/fnc bool-wrapper
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||
{::mf/wrap [#(mf/memo' % check-shape-props)]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
child-sel-ref (mf/use-memo
|
||||
(mf/deps (:id shape))
|
||||
#(refs/is-child-selected? (:id shape)))
|
||||
(let [shape (unchecked-get props "shape")
|
||||
shape-id (dm/get-prop shape :id)
|
||||
|
||||
childs-ref (mf/use-memo
|
||||
(mf/deps (:id shape))
|
||||
#(refs/select-bool-children (:id shape)))
|
||||
child-sel* (mf/with-memo [shape-id]
|
||||
(refs/is-child-selected? shape-id))
|
||||
|
||||
child-sel? (mf/deref child-sel-ref)
|
||||
childs (mf/deref childs-ref)
|
||||
childs* (mf/with-memo [shape-id]
|
||||
(refs/select-bool-children shape-id))
|
||||
|
||||
shape (cond-> shape
|
||||
child-sel?
|
||||
(dissoc :bool-content))]
|
||||
child-sel? (mf/deref child-sel*)
|
||||
childs (mf/deref childs*)
|
||||
|
||||
shape (cond-> shape
|
||||
^boolean child-sel?
|
||||
(dissoc :bool-content))]
|
||||
|
||||
[:> shape-container {:shape shape}
|
||||
[:& shape-component {:shape shape
|
||||
:childs childs}]]))))
|
||||
[:& bool-shape {:shape shape
|
||||
:childs childs}]]))))
|
||||
|
||||
|
|
|
@ -6,13 +6,30 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.common
|
||||
(:require
|
||||
[app.common.record :as cr]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private excluded-attrs
|
||||
#{:blocked
|
||||
:hide-fill-on-export
|
||||
:collapsed
|
||||
:remote-synced
|
||||
:exports})
|
||||
|
||||
(defn check-shape
|
||||
[new-shape old-shape]
|
||||
(cr/-equiv-with-exceptions old-shape new-shape excluded-attrs))
|
||||
|
||||
(defn check-shape-props
|
||||
[np op]
|
||||
(check-shape (unchecked-get np "shape")
|
||||
(unchecked-get op "shape")))
|
||||
|
||||
(defn generic-wrapper-factory
|
||||
[component]
|
||||
(mf/fnc generic-wrapper
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||
{::mf/wrap [#(mf/memo' % check-shape-props)]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")]
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.record :as cr]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -20,33 +19,18 @@
|
|||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.main.ui.shapes.text.fontfaces :as ff]
|
||||
[app.main.ui.workspace.shapes.common :refer [check-shape-props]]
|
||||
[app.main.ui.workspace.shapes.frame.dynamic-modifiers :as fdm]
|
||||
[app.main.ui.workspace.shapes.frame.node-store :as fns]
|
||||
[app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr]
|
||||
[beicon.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private excluded-attrs
|
||||
#{:blocked
|
||||
:hide-fill-on-export
|
||||
:collapsed
|
||||
:remote-synced
|
||||
:exports})
|
||||
|
||||
(defn check-shape
|
||||
[new-shape old-shape]
|
||||
(cr/-equiv-with-exceptions old-shape new-shape excluded-attrs))
|
||||
|
||||
(defn check-frame-props
|
||||
[np op]
|
||||
(check-shape (unchecked-get np "shape")
|
||||
(unchecked-get op "shape")))
|
||||
|
||||
(defn frame-shape-factory
|
||||
[shape-wrapper]
|
||||
(let [frame-shape (frame/frame-shape shape-wrapper)]
|
||||
(mf/fnc frame-shape-inner
|
||||
{::mf/wrap [#(mf/memo' % check-frame-props)]
|
||||
{::mf/wrap [#(mf/memo' % check-shape-props)]
|
||||
::mf/wrap-props false
|
||||
::mf/forward-ref true}
|
||||
[props ref]
|
||||
|
@ -66,8 +50,7 @@
|
|||
[new-props old-props]
|
||||
(and (= (unchecked-get new-props "thumbnail?")
|
||||
(unchecked-get old-props "thumbnail?"))
|
||||
(check-shape (unchecked-get new-props "shape")
|
||||
(unchecked-get old-props "shape"))))
|
||||
(check-shape-props new-props old-props)))
|
||||
|
||||
(defn nested-frame-wrapper-factory
|
||||
[shape-wrapper]
|
||||
|
@ -77,16 +60,18 @@
|
|||
{::mf/wrap [#(mf/memo' % check-props)]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame-id (:id shape)
|
||||
objects (wsh/lookup-page-objects @st/state)
|
||||
node-ref (mf/use-ref nil)
|
||||
modifiers-ref (mf/use-memo (mf/deps frame-id) #(refs/workspace-modifiers-by-frame-id frame-id))
|
||||
modifiers (mf/deref modifiers-ref)]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
objects (wsh/lookup-page-objects @st/state)
|
||||
|
||||
frame-id (dm/get-prop shape :id)
|
||||
|
||||
node-ref (mf/use-ref nil)
|
||||
modifiers* (mf/with-memo [frame-id]
|
||||
(refs/workspace-modifiers-by-frame-id frame-id))
|
||||
modifiers (mf/deref modifiers*)]
|
||||
|
||||
(fdm/use-dynamic-modifiers objects (mf/ref-val node-ref) modifiers)
|
||||
(let [shape (unchecked-get props "shape")]
|
||||
[:& frame-shape {:shape shape :ref node-ref}])))))
|
||||
[:& frame-shape {:shape shape :ref node-ref}]))))
|
||||
|
||||
|
||||
(defn root-frame-wrapper-factory
|
||||
|
@ -166,12 +151,9 @@
|
|||
:key "frame-container"
|
||||
:ref on-frame-load
|
||||
:opacity (when (:hidden shape) 0)}
|
||||
[:& ff/fontfaces-style {:fonts fonts}]
|
||||
[:& ff/fontfaces-style {:fonts fonts}]
|
||||
[:g.frame-thumbnail-wrapper
|
||||
{:id (dm/str "thumbnail-container-" frame-id)
|
||||
;; Hide the thumbnail when not displaying
|
||||
:opacity (when-not thumbnail? 0)}
|
||||
children]]
|
||||
|
||||
]))))
|
||||
|
||||
children]]]))))
|
||||
|
|
|
@ -6,33 +6,26 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.group
|
||||
(:require
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.util.dom :as dom]
|
||||
[app.main.ui.workspace.shapes.common :refer [check-shape-props]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn use-double-click [{:keys [id]}]
|
||||
(mf/use-callback
|
||||
(mf/deps id)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dw/select-inside-group id @ms/mouse-position)))))
|
||||
|
||||
(defn group-wrapper-factory
|
||||
[shape-wrapper]
|
||||
(let [group-shape (group/group-shape shape-wrapper)]
|
||||
(mf/fnc group-wrapper
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||
{::mf/wrap [#(mf/memo' % check-shape-props)]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape)))
|
||||
childs (mf/deref childs-ref)]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
shape-id (dm/get-prop shape :id)
|
||||
|
||||
childs* (mf/with-memo [shape-id]
|
||||
(refs/children-objects shape-id))
|
||||
childs (mf/deref childs*)]
|
||||
|
||||
[:> shape-container {:shape shape}
|
||||
[:& group-shape
|
||||
|
|
Loading…
Add table
Reference in a new issue