0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-25 07:58:49 -05:00

Merge pull request #3601 from penpot/niwinz-develop-experiments-4

  ♻️
This commit is contained in:
Alejandro 2023-09-07 11:38:59 +02:00 committed by GitHub
commit d7dea040af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 399 additions and 339 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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))}])])])))

View file

@ -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]]))

View file

@ -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}])])))

View file

@ -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))))

View file

@ -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))}])]]))))

View file

@ -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}]]]])))

View file

@ -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)

View file

@ -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)])))

View file

@ -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}]]))))

View file

@ -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")]

View file

@ -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]]]))))

View file

@ -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