mirror of
https://github.com/penpot/penpot.git
synced 2025-05-24 04:46:11 +02:00
⚡ Change resize to use DOM transformations
This commit is contained in:
parent
fa09fff2b5
commit
b2211aec59
38 changed files with 839 additions and 717 deletions
|
@ -18,7 +18,7 @@
|
|||
[app.main.ui.confirm]
|
||||
[app.main.ui.modal :refer [modal]]
|
||||
[app.main.ui.routes :as rt]
|
||||
[app.main.worker]
|
||||
[app.main.worker :as worker]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n]
|
||||
[app.util.theme :as theme]
|
||||
|
@ -60,6 +60,7 @@
|
|||
|
||||
(defn ^:export init
|
||||
[]
|
||||
(worker/init!)
|
||||
(sentry/init!)
|
||||
(i18n/init! cf/translations)
|
||||
(theme/init! cf/themes)
|
||||
|
|
|
@ -106,9 +106,9 @@
|
|||
;; apply-modifiers event is done, that consolidates all modifiers into the base
|
||||
;; geometric attributes of the shapes.
|
||||
|
||||
(declare set-modifiers-recursive)
|
||||
(declare set-local-displacement)
|
||||
(declare clear-local-transform)
|
||||
(declare set-modifiers-recursive)
|
||||
(declare get-ignore-tree)
|
||||
|
||||
(defn- set-modifiers
|
||||
([ids] (set-modifiers ids nil false))
|
||||
|
@ -119,21 +119,17 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
|
||||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)]
|
||||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
|
||||
|
||||
(reduce (fn [state id]
|
||||
(update state :workspace-modifiers
|
||||
#(set-modifiers-recursive %
|
||||
objects
|
||||
(get objects id)
|
||||
modifiers
|
||||
nil
|
||||
nil
|
||||
ignore-constraints)))
|
||||
state
|
||||
ids))))))
|
||||
setup-modifiers
|
||||
(fn [state id]
|
||||
(let [shape (get objects id)]
|
||||
(update state :workspace-modifiers
|
||||
#(set-modifiers-recursive % objects shape modifiers ignore-constraints))))]
|
||||
|
||||
(reduce setup-modifiers state ids))))))
|
||||
|
||||
;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
|
||||
(defn- set-rotation-modifiers
|
||||
|
@ -169,15 +165,14 @@
|
|||
children-ids (->> ids (mapcat #(cp/get-children % objects)))
|
||||
ids-with-children (d/concat-vec children-ids ids)
|
||||
object-modifiers (get state :workspace-modifiers)
|
||||
ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)]
|
||||
ignore-tree (get-ignore-tree object-modifiers objects ids)]
|
||||
|
||||
(rx/of (dwu/start-undo-transaction)
|
||||
(dch/update-shapes
|
||||
ids-with-children
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(merge (get object-modifiers (:id shape)))
|
||||
(gsh/transform-shape)))
|
||||
(let [modif (get object-modifiers (:id shape))]
|
||||
(gsh/transform-shape (merge shape modif))))
|
||||
{:reg-objects? true
|
||||
:ignore-tree ignore-tree
|
||||
;; Attributes that can change in the transform. This way we don't have to check
|
||||
|
@ -198,62 +193,51 @@
|
|||
"If the shape is a component instance, check its relative position respect the
|
||||
root of the component, and see if it changes after applying a transformation."
|
||||
[shape root transformed-shape transformed-root objects]
|
||||
(let [root (cond
|
||||
(:component-root? shape)
|
||||
shape
|
||||
(let [root
|
||||
(cond
|
||||
(:component-root? shape)
|
||||
shape
|
||||
|
||||
(nil? root)
|
||||
(cp/get-root-shape shape objects)
|
||||
(nil? root)
|
||||
(cp/get-root-shape shape objects)
|
||||
|
||||
:else root)
|
||||
:else root)
|
||||
|
||||
transformed-root (cond
|
||||
(:component-root? transformed-shape)
|
||||
transformed-shape
|
||||
transformed-root
|
||||
(cond
|
||||
(:component-root? transformed-shape)
|
||||
transformed-shape
|
||||
|
||||
(nil? transformed-root)
|
||||
(cp/get-root-shape transformed-shape objects)
|
||||
(nil? transformed-root)
|
||||
(cp/get-root-shape transformed-shape objects)
|
||||
|
||||
:else transformed-root)
|
||||
:else transformed-root)
|
||||
|
||||
shape-delta (when root
|
||||
(gpt/point (- (:x shape) (:x root))
|
||||
(- (:y shape) (:y root))))
|
||||
shape-delta
|
||||
(when root
|
||||
(gpt/point (- (:x shape) (:x root))
|
||||
(- (:y shape) (:y root))))
|
||||
|
||||
transformed-shape-delta (when transformed-root
|
||||
(gpt/point (- (:x transformed-shape) (:x transformed-root))
|
||||
(- (:y transformed-shape) (:y transformed-root))))
|
||||
transformed-shape-delta
|
||||
(when transformed-root
|
||||
(gpt/point (- (:x transformed-shape) (:x transformed-root))
|
||||
(- (:y transformed-shape) (:y transformed-root))))
|
||||
|
||||
ignore-geometry? (= shape-delta transformed-shape-delta)]
|
||||
|
||||
[root transformed-root ignore-geometry?]))
|
||||
|
||||
(defn- set-modifiers-recursive
|
||||
[modif-tree objects shape modifiers root transformed-root ignore-constraints]
|
||||
[modif-tree objects shape modifiers ignore-constraints]
|
||||
(let [children (map (d/getf objects) (:shapes shape))
|
||||
|
||||
transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers))
|
||||
|
||||
[root transformed-root ignore-geometry?]
|
||||
(check-delta shape root transformed-shape transformed-root objects)
|
||||
|
||||
modifiers (assoc modifiers :ignore-geometry? ignore-geometry?)
|
||||
|
||||
transformed-rect (gsh/calc-transformed-parent-rect shape modifiers)
|
||||
transformed-rect (gsh/transform-selrect (:selrect shape) modifiers)
|
||||
|
||||
set-child
|
||||
(fn [modif-tree child]
|
||||
(let [child-modifiers
|
||||
(gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
|
||||
|
||||
(let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
|
||||
(cond-> modif-tree
|
||||
(d/not-empty? (dissoc child-modifiers :ignore-geometry?))
|
||||
(set-modifiers-recursive objects
|
||||
child
|
||||
child-modifiers
|
||||
root
|
||||
transformed-root
|
||||
ignore-constraints))))
|
||||
(not (gsh/empty-modifiers? child-modifiers))
|
||||
(set-modifiers-recursive objects child child-modifiers ignore-constraints))))
|
||||
|
||||
modif-tree
|
||||
(-> modif-tree
|
||||
|
@ -261,13 +245,27 @@
|
|||
|
||||
(reduce set-child modif-tree children)))
|
||||
|
||||
(defn- set-local-displacement [point]
|
||||
(ptk/reify ::start-local-displacement
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [mtx (gmt/translate-matrix point)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :modifiers] {:displacement mtx}))))))
|
||||
(defn- get-ignore-tree
|
||||
"Retrieves a map with the flag `ignore-tree` given a tree of modifiers"
|
||||
([modif-tree objects shape]
|
||||
(get-ignore-tree modif-tree objects shape nil nil {}))
|
||||
|
||||
([modif-tree objects shape root transformed-root ignore-tree]
|
||||
(let [children (map (d/getf objects) (:shapes shape))
|
||||
|
||||
shape-id (:id shape)
|
||||
transformed-shape (gsh/transform-shape (merge shape (get modif-tree shape-id)))
|
||||
|
||||
[root transformed-root ignore-geometry?]
|
||||
(check-delta shape root transformed-shape transformed-root objects)
|
||||
|
||||
ignore-tree (assoc ignore-tree shape-id ignore-geometry?)
|
||||
|
||||
set-child
|
||||
(fn [modif-tree child]
|
||||
(get-ignore-tree modif-tree objects child root transformed-root ignore-tree))]
|
||||
|
||||
(reduce set-child ignore-tree children))))
|
||||
|
||||
(defn- clear-local-transform []
|
||||
(ptk/reify ::clear-local-transform
|
||||
|
@ -275,7 +273,7 @@
|
|||
(update [_ state]
|
||||
(-> state
|
||||
(dissoc :workspace-modifiers)
|
||||
(update :workspace-local dissoc :modifiers :current-move-selected)))))
|
||||
(update :workspace-local dissoc :current-move-selected)))))
|
||||
|
||||
|
||||
;; -- Resize --------------------------------------------------------
|
||||
|
@ -410,26 +408,13 @@
|
|||
(let [shape (get objects id)
|
||||
modifiers (gsh/resize-modifiers shape attr value)]
|
||||
(update state :workspace-modifiers
|
||||
#(set-modifiers-recursive %
|
||||
objects
|
||||
shape
|
||||
modifiers
|
||||
nil
|
||||
nil
|
||||
false))))
|
||||
#(set-modifiers-recursive % objects shape modifiers false))))
|
||||
state
|
||||
ids)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
;; TODO: looks completly redundant operation because
|
||||
;; apply-modifiers already finds all children.
|
||||
ids (d/concat-vec ids (mapcat #(cp/get-children % objects) ids))]
|
||||
(rx/of (apply-modifiers ids))))))
|
||||
|
||||
(watch [_ _ _]
|
||||
(rx/of (apply-modifiers ids)))))
|
||||
|
||||
;; -- Rotate --------------------------------------------------------
|
||||
|
||||
|
@ -579,11 +564,11 @@
|
|||
(->> position
|
||||
(rx/with-latest vector snap-delta)
|
||||
(rx/map snap/correct-snap-point)
|
||||
(rx/map set-local-displacement)
|
||||
(rx/map #(hash-map :displacement (gmt/translate-matrix %)))
|
||||
(rx/map (partial set-modifiers ids))
|
||||
(rx/take-until stopper))
|
||||
|
||||
(rx/of (set-modifiers ids)
|
||||
(apply-modifiers ids)
|
||||
(rx/of (apply-modifiers ids)
|
||||
(calculate-frame-for-move ids)
|
||||
(finish-transform)))))))))
|
||||
|
||||
|
@ -626,11 +611,11 @@
|
|||
(->> move-events
|
||||
(rx/take-until stopper)
|
||||
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
|
||||
(rx/map set-local-displacement))
|
||||
(rx/map #(hash-map :displacement (gmt/translate-matrix %)))
|
||||
(rx/map (partial set-modifiers selected)))
|
||||
(rx/of (move-selected direction shift?)))
|
||||
|
||||
(rx/of (set-modifiers selected)
|
||||
(apply-modifiers selected)
|
||||
(rx/of (apply-modifiers selected)
|
||||
(finish-transform))))
|
||||
(rx/empty))))))
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
[app.util.object :as obj]
|
||||
[app.util.timers :as ts]
|
||||
[cuerdas.core :as str]
|
||||
[debug :refer [debug?]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(def ^:private default-color clr/canvas)
|
||||
|
@ -76,10 +77,9 @@
|
|||
(let [shape-wrapper (shape-wrapper-factory objects)
|
||||
group-shape (group/group-shape shape-wrapper)]
|
||||
(mf/fnc group-wrapper
|
||||
[{:keys [shape frame] :as props}]
|
||||
[{:keys [shape] :as props}]
|
||||
(let [childs (mapv #(get objects %) (:shapes shape))]
|
||||
[:& group-shape {:frame frame
|
||||
:shape shape
|
||||
[:& group-shape {:shape shape
|
||||
:is-child-selected? true
|
||||
:childs childs}]))))
|
||||
|
||||
|
@ -88,11 +88,10 @@
|
|||
(let [shape-wrapper (shape-wrapper-factory objects)
|
||||
bool-shape (bool/bool-shape shape-wrapper)]
|
||||
(mf/fnc bool-wrapper
|
||||
[{:keys [shape frame] :as props}]
|
||||
[{:keys [shape] :as props}]
|
||||
(let [childs (->> (cp/get-children (:id shape) objects)
|
||||
(select-keys objects))]
|
||||
[:& bool-shape {:frame frame
|
||||
:shape shape
|
||||
[:& bool-shape {:shape shape
|
||||
:childs childs}]))))
|
||||
|
||||
(defn svg-raw-wrapper-factory
|
||||
|
@ -100,18 +99,16 @@
|
|||
(let [shape-wrapper (shape-wrapper-factory objects)
|
||||
svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
||||
(mf/fnc svg-raw-wrapper
|
||||
[{:keys [shape frame] :as props}]
|
||||
[{:keys [shape] :as props}]
|
||||
(let [childs (mapv #(get objects %) (:shapes shape))]
|
||||
(if (and (map? (:content shape))
|
||||
(or (= :svg (get-in shape [:content :tag]))
|
||||
(contains? shape :svg-attrs)))
|
||||
[:> shape-container {:shape shape}
|
||||
[:& svg-raw-shape {:frame frame
|
||||
:shape shape
|
||||
[:& svg-raw-shape {:shape shape
|
||||
:childs childs}]]
|
||||
|
||||
[:& svg-raw-shape {:frame frame
|
||||
:shape shape
|
||||
[:& svg-raw-shape {:shape shape
|
||||
:childs childs}])))))
|
||||
|
||||
(defn shape-wrapper-factory
|
||||
|
@ -123,8 +120,7 @@
|
|||
bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
|
||||
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (-> (gsh/transform-shape shape)
|
||||
(gsh/translate-to-frame frame))
|
||||
(let [shape (gsh/transform-shape shape)
|
||||
opts #js {:shape shape}
|
||||
svg-raw? (= :svg-raw (:type shape))]
|
||||
(if-not svg-raw?
|
||||
|
@ -198,7 +194,7 @@
|
|||
:width (:width item)
|
||||
:height (:height item)
|
||||
;; DEBUG
|
||||
;; :style {:filter "sepia(1)"}
|
||||
:style {:filter (when (debug? :thumbnails) "sepia(1)")}
|
||||
}]
|
||||
frame?
|
||||
[:& frame-wrapper {:shape item
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"A collection of derived refs."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
|
@ -232,23 +231,13 @@
|
|||
(l/derived #(get % id) workspace-page-objects))
|
||||
|
||||
(defn objects-by-id
|
||||
([ids]
|
||||
(objects-by-id ids nil))
|
||||
|
||||
([ids {:keys [with-modifiers?]
|
||||
:or { with-modifiers? false }}]
|
||||
(let [selector
|
||||
(fn [state]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
modifiers (:workspace-modifiers state)
|
||||
;; FIXME: Improve performance
|
||||
objects (cond-> objects
|
||||
with-modifiers?
|
||||
(gsh/merge-modifiers modifiers))
|
||||
xform (comp (map (d/getf objects))
|
||||
(remove nil?))]
|
||||
(into [] xform ids)))]
|
||||
(l/derived selector st/state =))))
|
||||
[ids]
|
||||
(let [selector
|
||||
(fn [state]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
xform (comp (map (d/getf objects)) (remove nil?))]
|
||||
(into [] xform ids)))]
|
||||
(l/derived selector st/state =)))
|
||||
|
||||
(defn- set-content-modifiers [state]
|
||||
(fn [id shape]
|
||||
|
@ -260,20 +249,8 @@
|
|||
(defn select-children [id]
|
||||
(let [selector
|
||||
(fn [state]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
|
||||
modifiers (-> (:workspace-modifiers state))
|
||||
{selected :selected disp-modifiers :modifiers}
|
||||
(-> (:workspace-local state)
|
||||
(select-keys [:modifiers :selected]))
|
||||
|
||||
modifiers
|
||||
(d/deep-merge
|
||||
modifiers
|
||||
(into {} (map #(vector % {:modifiers disp-modifiers})) selected))]
|
||||
|
||||
(let [objects (wsh/lookup-page-objects state)]
|
||||
(as-> (cp/select-children id objects) $
|
||||
(gsh/merge-modifiers $ modifiers)
|
||||
(d/mapm (set-content-modifiers state) $))))]
|
||||
(l/derived selector st/state =)))
|
||||
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
(ns app.main.ui.shapes.bool
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.path.bool :as pb]
|
||||
[app.common.path.shapes-to-path :as stp]
|
||||
[app.main.ui.hooks :refer [use-equal-memo]]
|
||||
|
@ -17,87 +14,26 @@
|
|||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc debug-bool
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [frame (obj/get props "frame")
|
||||
shape (obj/get props "shape")
|
||||
childs (obj/get props "childs")
|
||||
|
||||
[content-a content-b]
|
||||
(mf/use-memo
|
||||
(mf/deps shape childs)
|
||||
(fn []
|
||||
(let [childs (d/mapm #(-> %2 (gsh/translate-to-frame frame) gsh/transform-shape) childs)
|
||||
[content-a content-b]
|
||||
(->> (:shapes shape)
|
||||
(map #(get childs %))
|
||||
(filter #(not (:hidden %)))
|
||||
(map #(stp/convert-to-path % childs))
|
||||
(map :content)
|
||||
(map pb/close-paths)
|
||||
(map pb/add-previous))]
|
||||
(pb/content-intersect-split content-a content-b))))]
|
||||
[:g.debug-bool
|
||||
[:g.shape-a
|
||||
[:& path-shape {:shape (-> shape
|
||||
(assoc :type :path)
|
||||
(assoc :stroke-color "blue")
|
||||
(assoc :stroke-opacity 1)
|
||||
(assoc :stroke-width 1)
|
||||
(assoc :stroke-style :solid)
|
||||
(dissoc :fill-color :fill-opacity)
|
||||
(assoc :content content-b))
|
||||
:frame frame}]
|
||||
(for [{:keys [x y]} (gsp/content->points (pb/close-paths content-b))]
|
||||
[:circle {:cx x
|
||||
:cy y
|
||||
:r 2.5
|
||||
:style {:fill "blue"}}])]
|
||||
|
||||
[:g.shape-b
|
||||
[:& path-shape {:shape (-> shape
|
||||
(assoc :type :path)
|
||||
(assoc :stroke-color "red")
|
||||
(assoc :stroke-opacity 1)
|
||||
(assoc :stroke-width 0.5)
|
||||
(assoc :stroke-style :solid)
|
||||
(dissoc :fill-color :fill-opacity)
|
||||
(assoc :content content-a))
|
||||
:frame frame}]
|
||||
(for [{:keys [x y]} (gsp/content->points (pb/close-paths content-a))]
|
||||
[:circle {:cx x
|
||||
:cy y
|
||||
:r 1.25
|
||||
:style {:fill "red"}}])]])
|
||||
)
|
||||
|
||||
|
||||
(defn bool-shape
|
||||
[shape-wrapper]
|
||||
(mf/fnc bool-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [frame (obj/get props "frame")
|
||||
shape (obj/get props "shape")
|
||||
(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)
|
||||
|
||||
bool-content
|
||||
(mf/use-memo
|
||||
(mf/deps shape childs)
|
||||
(fn []
|
||||
(let [childs (d/mapm #(-> %2 gsh/transform-shape (gsh/translate-to-frame frame)) childs)]
|
||||
(->> (:shapes shape)
|
||||
(map #(get childs %))
|
||||
(filter #(not (:hidden %)))
|
||||
(map #(stp/convert-to-path % childs))
|
||||
(mapv :content)
|
||||
(pb/content-bool (:bool-type shape))))))]
|
||||
(->> (:shapes shape)
|
||||
(map #(get childs %))
|
||||
(filter #(not (:hidden %)))
|
||||
(map #(stp/convert-to-path % childs))
|
||||
(mapv :content)
|
||||
(pb/content-bool (:bool-type shape)))))]
|
||||
|
||||
[:*
|
||||
[:& path-shape {:shape (assoc shape :content bool-content)}]
|
||||
|
@ -105,10 +41,5 @@
|
|||
(when include-metadata?
|
||||
[:> "penpot:bool" {}
|
||||
(for [item (->> (:shapes shape) (mapv #(get childs %)))]
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape item
|
||||
:key (:id item)}])])
|
||||
|
||||
#_[:& debug-bool {:frame frame
|
||||
:shape shape
|
||||
:childs childs}]])))
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}])])])))
|
||||
|
|
|
@ -25,7 +25,12 @@
|
|||
(let [;; When not active the embedding we return the URI
|
||||
url-mapping (fn [obs]
|
||||
(if embed?
|
||||
(rx/merge-map http/fetch-data-uri obs)
|
||||
(->> obs
|
||||
(rx/merge-map
|
||||
(fn [uri]
|
||||
(->> (http/fetch-data-uri uri true)
|
||||
;; If fetching give an error we store the URI as its `data-uri`
|
||||
(rx/catch #(rx/of (hash-map uri uri)))))))
|
||||
(rx/map identity obs)))
|
||||
|
||||
sub (->> (rx/from urls)
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
filter-x (min x (+ x offset-x (- spread) (- blur) -5))
|
||||
filter-y (min y (+ y offset-y (- spread) (- blur) -5))
|
||||
filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10)
|
||||
filter-height (+ height (mth/abs offset-x) (* spread 2) (* blur 2) 10)]
|
||||
filter-height (+ height (mth/abs offset-y) (* spread 2) (* blur 2) 10)]
|
||||
{:x1 filter-x
|
||||
:y1 filter-y
|
||||
:x2 (+ filter-x filter-width)
|
||||
|
@ -208,26 +208,31 @@
|
|||
margin (gsh/shape-stroke-margin shape stroke-width)]
|
||||
(+ stroke-width margin)))
|
||||
|
||||
(defn change-filter-in
|
||||
"Adds the previous filter as `filter-in` parameter"
|
||||
[filters]
|
||||
(map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters))))
|
||||
|
||||
(mf/defc filters
|
||||
[{:keys [filter-id shape]}]
|
||||
|
||||
(let [filters (shape->filters shape)
|
||||
|
||||
;; Adds the previous filter as `filter-in` parameter
|
||||
filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))
|
||||
bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
|
||||
padding (calculate-padding shape)]
|
||||
|
||||
(let [filters (-> shape shape->filters change-filter-in)
|
||||
bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
|
||||
padding (calculate-padding shape)
|
||||
selrect (:selrect shape)
|
||||
filter-x (/ (- (:x bounds) (:x selrect) padding) (:width selrect))
|
||||
filter-y (/ (- (:y bounds) (:y selrect) padding) (:height selrect))
|
||||
filter-width (/ (+ (:width bounds) (* 2 padding)) (:width selrect))
|
||||
filter-height (/ (+ (:height bounds) (* 2 padding)) (:height selrect))]
|
||||
[:*
|
||||
(when (> (count filters) 2)
|
||||
[:filter {:id filter-id
|
||||
:x (- (:x bounds) padding)
|
||||
:y (- (:y bounds) padding)
|
||||
:width (+ (:width bounds) (* 2 padding))
|
||||
:height (+ (:height bounds) (* 2 padding))
|
||||
:filterUnits "userSpaceOnUse"
|
||||
[:filter {:id filter-id
|
||||
:x filter-x
|
||||
:y filter-y
|
||||
:width filter-width
|
||||
:height filter-height
|
||||
:filterUnits "objectBoundingBox"
|
||||
:color-interpolation-filters "sRGB"}
|
||||
|
||||
(for [entry filters]
|
||||
[:& filter-entry {:entry entry}])])]))
|
||||
|
||||
|
|
|
@ -10,6 +10,22 @@
|
|||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn frame-clip-id
|
||||
[shape render-id]
|
||||
(str "frame-clip-" (:id shape) "-" render-id))
|
||||
|
||||
(defn frame-clip-url
|
||||
[shape render-id]
|
||||
(when (= :frame (:type shape))
|
||||
(str "url(#" (frame-clip-id shape render-id) ")")))
|
||||
|
||||
(mf/defc frame-clip-def
|
||||
[{:keys [shape render-id]}]
|
||||
(when (= :frame (:type shape))
|
||||
(let [{:keys [x y width height]} shape]
|
||||
[:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"}
|
||||
[:rect {:x x :y y :width width :height height}]])))
|
||||
|
||||
(defn frame-shape
|
||||
[shape-wrapper]
|
||||
(mf/fnc frame-shape
|
||||
|
@ -17,7 +33,7 @@
|
|||
[props]
|
||||
(let [childs (unchecked-get props "childs")
|
||||
shape (unchecked-get props "shape")
|
||||
{:keys [width height]} shape
|
||||
{:keys [x y width height]} shape
|
||||
|
||||
has-background? (or (some? (:fill-color shape))
|
||||
(some? (:fill-color-gradient shape)))
|
||||
|
@ -25,8 +41,8 @@
|
|||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x 0
|
||||
:y 0
|
||||
#js {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:className "frame-background"}))]
|
||||
|
@ -34,7 +50,6 @@
|
|||
(when (or has-background? has-stroke?)
|
||||
[:> :rect props])
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame shape
|
||||
:shape item
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}])])))
|
||||
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
(mf/fnc group-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [frame (unchecked-get props "frame")
|
||||
shape (unchecked-get props "shape")
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
render-id (mf/use-ctx muc/render-ctx)
|
||||
masked-group? (:masked-group? shape)
|
||||
|
@ -46,11 +45,10 @@
|
|||
[:> clip-wrapper clip-props
|
||||
[:> mask-wrapper mask-props
|
||||
(when masked-group?
|
||||
[:> render-mask #js {:frame frame :mask mask}])
|
||||
[:> render-mask #js {:mask mask}])
|
||||
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape item
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}])]]))))
|
||||
|
||||
|
||||
|
|
|
@ -34,13 +34,9 @@
|
|||
(mf/fnc mask-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [frame (unchecked-get props "frame")
|
||||
mask (unchecked-get props "mask")
|
||||
(let [mask (unchecked-get props "mask")
|
||||
render-id (mf/use-ctx muc/render-ctx)
|
||||
|
||||
mask' (-> mask
|
||||
(gsh/transform-shape)
|
||||
(gsh/translate-to-frame frame))]
|
||||
mask' (gsh/transform-shape mask)]
|
||||
[:defs
|
||||
[:filter {:id (filter-id render-id mask)}
|
||||
[:feFlood {:flood-color "white"
|
||||
|
@ -52,13 +48,13 @@
|
|||
;; Clip path is necessary so the elements inside the mask won't affect
|
||||
;; the events outside. Clip hides the elements but mask doesn't (like display vs visibility)
|
||||
;; we cannot use clips instead of mask because clips can only be simple shapes
|
||||
[:clipPath {:id (clip-id render-id mask)}
|
||||
[:clipPath {:class "mask-clip-path"
|
||||
:id (clip-id render-id mask)}
|
||||
[:polyline {:points (->> (:points mask')
|
||||
(map #(str (:x %) "," (:y %)))
|
||||
(str/join " "))}]]
|
||||
[:mask {:id (mask-id render-id mask)}
|
||||
[:mask {:class "mask-shape"
|
||||
:id (mask-id render-id mask)}
|
||||
[:g {:filter (filter-url render-id mask)}
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape (-> mask
|
||||
(dissoc :shadow :blur))}]]]])))
|
||||
[:& shape-wrapper {:shape (dissoc mask :shadow :blur)}]]]])))
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
[app.main.ui.shapes.export :as ed]
|
||||
[app.main.ui.shapes.fill-image :as fim]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.gradients :as grad]
|
||||
[app.main.ui.shapes.svg-defs :as defs]
|
||||
[app.util.object :as obj]
|
||||
|
@ -26,6 +27,8 @@
|
|||
(let [shape (obj/get props "shape")
|
||||
children (obj/get props "children")
|
||||
pointer-events (obj/get props "pointer-events")
|
||||
|
||||
type (:type shape)
|
||||
render-id (mf/use-memo #(str (uuid/next)))
|
||||
filter-id (str "filter_" render-id)
|
||||
styles (-> (obj/new)
|
||||
|
@ -34,10 +37,6 @@
|
|||
(cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal))
|
||||
(obj/set! "mixBlendMode" (d/name (:blend-mode shape)))))
|
||||
|
||||
{:keys [x y width height type]} shape
|
||||
frame? (= :frame type)
|
||||
group? (= :group type)
|
||||
|
||||
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
|
||||
|
||||
wrapper-props
|
||||
|
@ -50,26 +49,14 @@
|
|||
|
||||
wrapper-props
|
||||
(cond-> wrapper-props
|
||||
frame?
|
||||
(-> (obj/set! "x" x)
|
||||
(obj/set! "y" y)
|
||||
(obj/set! "width" width)
|
||||
(obj/set! "height" height)
|
||||
(obj/set! "xmlns" "http://www.w3.org/2000/svg")
|
||||
(obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink")
|
||||
(cond->
|
||||
include-metadata?
|
||||
(obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))
|
||||
(= :frame type)
|
||||
(obj/set! "clipPath" (frame/frame-clip-url shape render-id))
|
||||
|
||||
wrapper-props
|
||||
(cond-> wrapper-props
|
||||
group?
|
||||
(attrs/add-style-attrs shape))
|
||||
|
||||
wrapper-tag (if frame? "svg" "g")]
|
||||
(= :group type)
|
||||
(attrs/add-style-attrs shape))]
|
||||
|
||||
[:& (mf/provider muc/render-ctx) {:value render-id}
|
||||
[:> wrapper-tag wrapper-props
|
||||
[:> :g wrapper-props
|
||||
(when include-metadata?
|
||||
[:& ed/export-data {:shape shape}])
|
||||
|
||||
|
@ -79,5 +66,6 @@
|
|||
[:& grad/gradient {:shape shape :attr :fill-color-gradient}]
|
||||
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
|
||||
[:& fim/fill-image-pattern {:shape shape :render-id render-id}]
|
||||
[:& cs/stroke-defs {:shape shape :render-id render-id}]]
|
||||
[:& cs/stroke-defs {:shape shape :render-id render-id}]
|
||||
[:& frame/frame-clip-def {:shape shape :render-id render-id}]]
|
||||
children]]))
|
||||
|
|
|
@ -88,8 +88,7 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [frame (unchecked-get props "frame")
|
||||
shape (unchecked-get props "shape")
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
|
||||
{:keys [content]} shape
|
||||
|
@ -103,12 +102,12 @@
|
|||
svg-root?
|
||||
[:& svg-root {:shape shape}
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame :shape item :key (:id item)}])]
|
||||
[:& shape-wrapper {:shape item :key (:id item)}])]
|
||||
|
||||
svg-tag?
|
||||
[:& svg-element {:shape shape}
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame :shape item :key (:id item)}])]
|
||||
[:& shape-wrapper {:shape item :key (:id item)}])]
|
||||
|
||||
svg-leaf?
|
||||
content
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
others are defined using a generic wrapper implemented in
|
||||
common."
|
||||
(:require
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -81,16 +80,12 @@
|
|||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (obj/get props "shape")
|
||||
frame (obj/get props "frame")
|
||||
shape (geom/translate-to-frame shape frame)
|
||||
|
||||
opts #js {:shape shape
|
||||
:frame frame}
|
||||
opts #js {:shape shape}
|
||||
|
||||
svg-element? (and (= (:type shape) :svg-raw)
|
||||
(not= :svg (get-in shape [:content :tag])))]
|
||||
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(when (and (some? shape) (not (:hidden shape)))
|
||||
[:*
|
||||
(if-not svg-element?
|
||||
(case (:type shape)
|
||||
|
@ -104,7 +99,7 @@
|
|||
:bool [:> bool-wrapper opts]
|
||||
|
||||
;; Only used when drawing a new frame.
|
||||
:frame [:> frame-wrapper {:shape shape}]
|
||||
:frame [:> frame-wrapper opts]
|
||||
|
||||
nil)
|
||||
|
||||
|
@ -112,7 +107,7 @@
|
|||
[:> svg-raw-wrapper opts])
|
||||
|
||||
(when (debug? :bounding-boxes)
|
||||
[:& bounding-box {:shape shape :frame frame}])])))
|
||||
[:> bounding-box opts])])))
|
||||
|
||||
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
|
||||
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
||||
|
|
|
@ -27,12 +27,10 @@
|
|||
[shape-wrapper]
|
||||
(let [shape-component (bool/bool-shape shape-wrapper)]
|
||||
(mf/fnc bool-wrapper
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
|
||||
childs-ref (mf/use-memo
|
||||
(mf/deps (:id shape))
|
||||
#(refs/select-children (:id shape)))
|
||||
|
@ -41,7 +39,6 @@
|
|||
|
||||
[:> shape-container {:shape shape}
|
||||
[:& shape-component
|
||||
{:frame frame
|
||||
:shape shape
|
||||
{:shape shape
|
||||
:childs childs}]]))))
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
(ns app.main.ui.workspace.shapes.frame
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
|
|
|
@ -27,18 +27,15 @@
|
|||
[shape-wrapper]
|
||||
(let [group-shape (group/group-shape shape-wrapper)]
|
||||
(mf/fnc group-wrapper
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
|
||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape) {:with-modifiers? true}))
|
||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
||||
childs (mf/deref childs-ref)]
|
||||
|
||||
[:> shape-container {:shape shape}
|
||||
[:& group-shape
|
||||
{:frame frame
|
||||
:shape shape
|
||||
{:shape shape
|
||||
:childs childs}]]))))
|
||||
|
||||
|
|
|
@ -15,12 +15,10 @@
|
|||
[shape-wrapper]
|
||||
(let [svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
||||
(mf/fnc svg-raw-wrapper
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
|
||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
||||
childs (mf/deref childs-ref)]
|
||||
|
||||
|
@ -28,11 +26,9 @@
|
|||
(if (or (= (get-in shape [:content :tag]) :svg)
|
||||
(and (contains? shape :svg-attrs) (map? (:content shape))))
|
||||
[:> shape-container {:shape shape}
|
||||
[:& svg-raw-shape {:frame frame
|
||||
:shape shape
|
||||
[:& svg-raw-shape {:shape shape
|
||||
:childs childs}]]
|
||||
|
||||
[:& svg-raw-shape {:frame frame
|
||||
:shape shape
|
||||
[:& svg-raw-shape {:shape shape
|
||||
:childs childs}])))))
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
;; that the new parameter is sent
|
||||
{:keys [edit-path
|
||||
edition
|
||||
modifiers
|
||||
options-mode
|
||||
panning
|
||||
picking-color?
|
||||
|
@ -62,10 +61,10 @@
|
|||
drawing (mf/deref refs/workspace-drawing)
|
||||
options (mf/deref refs/workspace-page-options)
|
||||
base-objects (mf/deref refs/workspace-page-objects)
|
||||
object-modifiers (mf/deref refs/workspace-modifiers)
|
||||
objects (mf/use-memo
|
||||
(mf/deps base-objects object-modifiers)
|
||||
#(gsh/merge-modifiers base-objects object-modifiers))
|
||||
modifiers (mf/deref refs/workspace-modifiers)
|
||||
objects-modified (mf/use-memo
|
||||
(mf/deps base-objects modifiers)
|
||||
#(gsh/merge-modifiers base-objects modifiers))
|
||||
background (get options :background clr/canvas)
|
||||
|
||||
;; STATE
|
||||
|
@ -81,7 +80,6 @@
|
|||
|
||||
;; REFS
|
||||
viewport-ref (mf/use-ref nil)
|
||||
render-ref (mf/use-ref nil)
|
||||
|
||||
;; VARS
|
||||
disable-paste (mf/use-var false)
|
||||
|
@ -94,25 +92,22 @@
|
|||
drawing-tool (:tool drawing)
|
||||
drawing-obj (:object drawing)
|
||||
|
||||
selected-shapes (into []
|
||||
(comp (map #(get objects %))
|
||||
(filter some?))
|
||||
selected)
|
||||
xf-select-shape (comp (map (d/getf objects-modified)) (filter some?))
|
||||
selected-shapes (into [] xf-select-shape selected)
|
||||
selected-frames (into #{} (map :frame-id) selected-shapes)
|
||||
|
||||
;; Only when we have all the selected shapes in one frame
|
||||
selected-frame (when (= (count selected-frames) 1) (get objects (first selected-frames)))
|
||||
|
||||
selected-frame (when (= (count selected-frames) 1) (get base-objects (first selected-frames)))
|
||||
|
||||
create-comment? (= :comments drawing-tool)
|
||||
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
|
||||
(and (some? drawing-obj) (= :path (:type drawing-obj))))
|
||||
node-editing? (and edition (not= :text (get-in objects [edition :type])))
|
||||
text-editing? (and edition (= :text (get-in objects [edition :type])))
|
||||
node-editing? (and edition (not= :text (get-in base-objects [edition :type])))
|
||||
text-editing? (and edition (= :text (get-in base-objects [edition :type])))
|
||||
|
||||
on-click (actions/on-click hover selected edition drawing-path? drawing-tool)
|
||||
on-context-menu (actions/on-context-menu hover)
|
||||
on-double-click (actions/on-double-click hover hover-ids drawing-path? objects edition)
|
||||
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition)
|
||||
on-drag-enter (actions/on-drag-enter)
|
||||
on-drag-over (actions/on-drag-over)
|
||||
on-drop (actions/on-drop file viewport-ref zoom)
|
||||
|
@ -155,10 +150,10 @@
|
|||
(hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? node-editing?)
|
||||
(hooks/setup-resize layout viewport-ref)
|
||||
(hooks/setup-keyboard alt? ctrl? space?)
|
||||
(hooks/setup-hover-shapes page-id move-stream objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
|
||||
(hooks/setup-viewport-modifiers modifiers selected objects render-ref)
|
||||
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
|
||||
(hooks/setup-viewport-modifiers modifiers base-objects)
|
||||
(hooks/setup-shortcuts node-editing? drawing-path?)
|
||||
(hooks/setup-active-frames objects vbox hover active-frames)
|
||||
(hooks/setup-active-frames base-objects vbox hover active-frames)
|
||||
|
||||
[:div.viewport
|
||||
[:div.viewport-overlays
|
||||
|
@ -184,7 +179,6 @@
|
|||
[:& widgets/viewport-actions]]
|
||||
[:svg.render-shapes
|
||||
{:id "render"
|
||||
:ref render-ref
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns:penpot "https://penpot.app/xmlns"
|
||||
|
@ -202,7 +196,7 @@
|
|||
[:& (mf/provider embed/context) {:value true}
|
||||
;; Render root shape
|
||||
[:& shapes/root-shape {:key page-id
|
||||
:objects objects
|
||||
:objects base-objects
|
||||
:active-frames @active-frames}]]]]
|
||||
|
||||
[:svg.viewport-controls
|
||||
|
@ -234,7 +228,7 @@
|
|||
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
|
||||
(when show-outlines?
|
||||
[:& outline/shape-outlines
|
||||
{:objects objects
|
||||
{:objects base-objects
|
||||
:selected selected
|
||||
:hover (when (not= :frame (:type @hover))
|
||||
#{(or @frame-hover (:id @hover))})
|
||||
|
@ -259,10 +253,10 @@
|
|||
:zoom zoom}])
|
||||
|
||||
(when text-editing?
|
||||
[:& editor/text-shape-edit {:shape (get objects edition)}])
|
||||
[:& editor/text-shape-edit {:shape (get base-objects edition)}])
|
||||
|
||||
[:& widgets/frame-titles
|
||||
{:objects objects
|
||||
{:objects objects-modified
|
||||
:selected selected
|
||||
:zoom zoom
|
||||
:modifiers modifiers
|
||||
|
@ -273,7 +267,7 @@
|
|||
(when show-prototypes?
|
||||
[:& widgets/frame-flows
|
||||
{:flows (:flows options)
|
||||
:objects objects
|
||||
:objects base-objects
|
||||
:selected selected
|
||||
:zoom zoom
|
||||
:modifiers modifiers
|
||||
|
@ -310,7 +304,7 @@
|
|||
:zoom zoom
|
||||
:page-id page-id
|
||||
:selected selected
|
||||
:objects objects
|
||||
:objects base-objects
|
||||
:modifiers modifiers}])
|
||||
|
||||
(when show-snap-distance?
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui.workspace.viewport.hooks
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages :as cp]
|
||||
[app.main.data.shortcuts :as dsc]
|
||||
|
@ -169,22 +170,31 @@
|
|||
(reset! hover hover-shape)
|
||||
(reset! hover-ids ids))))))
|
||||
|
||||
(defn setup-viewport-modifiers [modifiers selected objects render-ref]
|
||||
(let [roots (mf/use-memo
|
||||
(mf/deps objects selected)
|
||||
(fn []
|
||||
(let [roots-ids (cp/clean-loops objects selected)]
|
||||
(->> roots-ids (mapv #(get objects %))))))]
|
||||
(defn setup-viewport-modifiers
|
||||
[modifiers objects]
|
||||
(let [transforms
|
||||
(mf/use-memo
|
||||
(mf/deps modifiers)
|
||||
(fn []
|
||||
(d/mapm (fn [id {modifiers :modifiers}]
|
||||
(let [center (gsh/center-shape (get objects id))]
|
||||
(gsh/modifiers->transform center modifiers)))
|
||||
modifiers)))
|
||||
|
||||
shapes
|
||||
(mf/use-memo
|
||||
(mf/deps transforms)
|
||||
(fn []
|
||||
(->> (keys transforms)
|
||||
(mapv (d/getf objects)))))]
|
||||
|
||||
;; Layout effect is important so the code is executed before the modifiers
|
||||
;; are applied to the shape
|
||||
(mf/use-layout-effect
|
||||
(mf/deps modifiers roots)
|
||||
|
||||
#(when-let [render-node (mf/ref-val render-ref)]
|
||||
(if modifiers
|
||||
(utils/update-transform render-node roots modifiers)
|
||||
(utils/remove-transform render-node roots))))))
|
||||
(mf/deps transforms)
|
||||
(fn []
|
||||
(utils/update-transform shapes transforms modifiers)
|
||||
#(utils/remove-transform shapes)))))
|
||||
|
||||
(defn inside-vbox [vbox objects frame-id]
|
||||
(let [frame (get objects frame-id)]
|
||||
|
|
|
@ -320,7 +320,9 @@
|
|||
(let [shape-id (:id shape)
|
||||
shape (geom/transform-shape shape {:round-coords? false})
|
||||
|
||||
shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape)
|
||||
shape' (if (debug? :simple-selection)
|
||||
(geom/setup {:type :rect} (geom/selection-rect [shape]))
|
||||
shape)
|
||||
|
||||
on-resize
|
||||
(fn [current-position _initial-position event]
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
(->> shapes (first)))
|
||||
|
||||
shape (if modifiers
|
||||
(-> shape (assoc :modifiers modifiers) gsh/transform-shape)
|
||||
(-> shape (merge (get modifiers (:id shape))) gsh/transform-shape)
|
||||
shape)
|
||||
|
||||
frame-id (snap/snap-frame-id shapes)]
|
||||
|
|
|
@ -32,8 +32,16 @@
|
|||
loading-node (when frame-node
|
||||
(dom/query frame-node "[data-loading=\"true\"]"))]
|
||||
(if (and (some? frame-node) (not (some? loading-node)))
|
||||
(let [xml (-> (js/XMLSerializer.)
|
||||
(.serializeToString frame-node)
|
||||
(let [frame-html (-> (js/XMLSerializer.)
|
||||
(.serializeToString frame-node))
|
||||
|
||||
;; We need to wrap the group node into a SVG with a viewbox that matches the selrect of the frame
|
||||
svg-node (.createElementNS js/document "http://www.w3.org/2000/svg" "svg")
|
||||
_ (.setAttribute svg-node "version" "1.1")
|
||||
_ (.setAttribute svg-node "viewBox" (str (:x shape) " " (:y shape) " " (:width shape) " " (:height shape)))
|
||||
_ (unchecked-set svg-node "innerHTML" frame-html)
|
||||
xml (-> (js/XMLSerializer.)
|
||||
(.serializeToString svg-node)
|
||||
js/encodeURIComponent
|
||||
js/unescape
|
||||
js/btoa)
|
||||
|
|
|
@ -7,35 +7,89 @@
|
|||
(ns app.main.ui.workspace.viewport.utils
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.main.ui.cursors :as cur]
|
||||
[app.util.dom :as dom]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; TODO: looks like first argument is not necessary.
|
||||
(defn update-transform [_node shapes modifiers]
|
||||
(doseq [{:keys [id type]} shapes]
|
||||
(let [shape-node (dom/get-element (str "shape-" id))
|
||||
(defn- text-corrected-transform
|
||||
"If we apply a scale directly to the texts it will show deformed so we need to create this
|
||||
correction matrix to \"undo\" the resize but keep the other transformations."
|
||||
[{:keys [points transform transform-inverse]} current-transform modifiers]
|
||||
|
||||
;; When the shape is a frame we maybe need to move its thumbnail
|
||||
thumb-node (dom/get-element (str "thumbnail-" id))]
|
||||
(when-let [node (cond
|
||||
(and (some? shape-node) (= :frame type))
|
||||
(.-parentNode shape-node)
|
||||
(let [corner-pt (first points)
|
||||
transform (or transform (gmt/matrix))
|
||||
transform-inverse (or transform-inverse (gmt/matrix))
|
||||
|
||||
(and (some? thumb-node) (= :frame type))
|
||||
(.-parentNode thumb-node)
|
||||
current-transform
|
||||
(if (some? (:resize-vector modifiers))
|
||||
(gmt/multiply
|
||||
current-transform
|
||||
transform
|
||||
(gmt/scale-matrix (gpt/inverse (:resize-vector modifiers)) (gpt/transform corner-pt transform-inverse))
|
||||
transform-inverse)
|
||||
current-transform)
|
||||
|
||||
:else
|
||||
shape-node)]
|
||||
(dom/set-attribute node "transform" (str (:displacement modifiers)))))))
|
||||
current-transform
|
||||
(if (some? (:resize-vector-2 modifiers))
|
||||
(gmt/multiply
|
||||
current-transform
|
||||
transform
|
||||
(gmt/scale-matrix (gpt/inverse (:resize-vector-2 modifiers)) (gpt/transform corner-pt transform-inverse))
|
||||
transform-inverse)
|
||||
current-transform)]
|
||||
current-transform))
|
||||
|
||||
;; TODO: looks like first argument is not necessary.
|
||||
(defn remove-transform [_node shapes]
|
||||
(doseq [{:keys [id type]} shapes]
|
||||
(when-let [node (dom/get-element (str "shape-" id))]
|
||||
(let [node (if (= :frame type) (.-parentNode node) node)]
|
||||
(dom/remove-attribute node "transform")))))
|
||||
(defn get-nodes
|
||||
"Retrieve the DOM nodes to apply the matrix transformation"
|
||||
[{:keys [id type masked-group?]}]
|
||||
(let [shape-node (dom/get-element (str "shape-" id))
|
||||
|
||||
frame? (= :frame type)
|
||||
group? (= :group type)
|
||||
mask? (and group? masked-group?)
|
||||
|
||||
;; When the shape is a frame we maybe need to move its thumbnail
|
||||
thumb-node (when frame? (dom/get-element (str "thumbnail-" id)))]
|
||||
(cond
|
||||
(some? thumb-node)
|
||||
[(.-parentNode thumb-node)]
|
||||
|
||||
(and (some? shape-node) frame?)
|
||||
[(dom/query shape-node ".frame-background")
|
||||
(dom/query shape-node ".frame-clip")]
|
||||
|
||||
;; For groups we don't want to transform the whole group but only
|
||||
;; its filters/masks
|
||||
(and (some? shape-node) mask?)
|
||||
[(dom/query shape-node ".mask-clip-path")
|
||||
(dom/query shape-node ".mask-shape")]
|
||||
|
||||
group?
|
||||
[]
|
||||
|
||||
:else
|
||||
[shape-node])))
|
||||
|
||||
(defn update-transform [shapes transforms modifiers]
|
||||
(doseq [{id :id :as shape} shapes]
|
||||
(when-let [nodes (get-nodes shape)]
|
||||
(let [transform (get transforms id)
|
||||
modifiers (get-in modifiers [id :modifiers])
|
||||
transform (case type
|
||||
:text (text-corrected-transform shape transform modifiers)
|
||||
transform)]
|
||||
(doseq [node nodes]
|
||||
(when (and (some? transform) (some? node))
|
||||
(dom/set-attribute node "transform" (str transform))))))))
|
||||
|
||||
(defn remove-transform [shapes]
|
||||
(doseq [shape shapes]
|
||||
(when-let [nodes (get-nodes shape)]
|
||||
(doseq [node nodes]
|
||||
(when (some? node)
|
||||
(dom/remove-attribute node "transform"))))))
|
||||
|
||||
(defn format-viewbox [vbox]
|
||||
(str/join " " [(+ (:x vbox 0) (:left-offset vbox 0))
|
||||
|
|
|
@ -13,18 +13,22 @@
|
|||
[error]
|
||||
(js/console.error "Error on worker" error))
|
||||
|
||||
(defonce instance
|
||||
(when (not= *target* "nodejs")
|
||||
(uw/init cfg/worker-uri on-error)))
|
||||
(defonce instance (atom nil))
|
||||
|
||||
(defn init!
|
||||
[]
|
||||
(reset!
|
||||
instance
|
||||
(uw/init cfg/worker-uri on-error)))
|
||||
|
||||
(defn ask!
|
||||
[message]
|
||||
(uw/ask! instance message))
|
||||
(when @instance (uw/ask! @instance message)))
|
||||
|
||||
(defn ask-buffered!
|
||||
[message]
|
||||
(uw/ask-buffered! instance message))
|
||||
(when @instance (uw/ask-buffered! @instance message)))
|
||||
|
||||
(defn ask-many!
|
||||
[message]
|
||||
(uw/ask-many! instance message))
|
||||
(when @instance (uw/ask-many! @instance message)))
|
||||
|
|
|
@ -162,16 +162,30 @@
|
|||
(->> (rx/take 1 observable)
|
||||
(rx/subs resolve reject)))))
|
||||
|
||||
(defn fetch-data-uri [uri]
|
||||
(c/with-cache {:key uri :max-age (dt/duration {:hours 4})}
|
||||
(->> (send! {:method :get
|
||||
:uri uri
|
||||
:response-type :blob
|
||||
:omit-default-headers true})
|
||||
(rx/filter #(= 200 (:status %)))
|
||||
(rx/map :body)
|
||||
(rx/mapcat wapi/read-file-as-data-url)
|
||||
(rx/map #(hash-map uri %)))))
|
||||
(defn fetch-data-uri
|
||||
([uri]
|
||||
(fetch-data-uri uri false))
|
||||
|
||||
([uri throw-err?]
|
||||
(c/with-cache {:key uri :max-age (dt/duration {:hours 4})}
|
||||
(let [request-stream
|
||||
(send! {:method :get
|
||||
:uri uri
|
||||
:response-type :blob
|
||||
:omit-default-headers true})
|
||||
|
||||
request-stream
|
||||
(if throw-err?
|
||||
(rx/tap #(when-not (and (>= (:status %) 200) (< (:status %) 300))
|
||||
;; HTTP ERRROR
|
||||
(throw (js/Error. "Error fetching data uri" #js {:cause (clj->js %)})))
|
||||
request-stream)
|
||||
(rx/filter #(= 200 (:status %))
|
||||
request-stream))]
|
||||
(->> request-stream
|
||||
(rx/map :body)
|
||||
(rx/mapcat wapi/read-file-as-data-url)
|
||||
(rx/map #(hash-map uri %)))))))
|
||||
|
||||
(defn fetch-text [url]
|
||||
(c/with-cache {:key url :max-age (dt/duration {:hours 4})}
|
||||
|
|
|
@ -105,3 +105,29 @@
|
|||
:onRender on-render}
|
||||
children]
|
||||
children)))
|
||||
|
||||
(defn benchmark
|
||||
[& {:keys [f warmup iterations name]
|
||||
:or {iterations 10000}}]
|
||||
(let [end-mark (str name ":end")]
|
||||
(println "=> benchmarking:" name)
|
||||
(println "--> warming up:" iterations)
|
||||
(loop [i iterations]
|
||||
(when (pos? i)
|
||||
(f)
|
||||
(recur (dec i))))
|
||||
(println "--> benchmarking:" iterations)
|
||||
(js/performance.mark name)
|
||||
(loop [i iterations]
|
||||
(when (pos? i)
|
||||
(f)
|
||||
(recur (dec i))))
|
||||
(js/performance.measure end-mark name)
|
||||
(let [[result] (js/performance.getEntriesByName end-mark)
|
||||
duration (mth/precision (.-duration ^js result) 4)
|
||||
avg (mth/precision (/ duration iterations) 4)]
|
||||
(println "--> TOTAL:" (str duration "ms") "AVG:" (str avg "ms"))
|
||||
(js/performance.clearMarks name)
|
||||
(js/performance.clearMeasures end-mark)
|
||||
#js {:duration duration
|
||||
:avg avg})))
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def ^:const current-version 2)
|
||||
|
||||
(defn create-manifest
|
||||
"Creates a manifest entry for the given files"
|
||||
[team-id file-id export-type files]
|
||||
|
@ -41,6 +43,7 @@
|
|||
:shared is-shared
|
||||
:pages pages
|
||||
:pagesIndex index
|
||||
:version current-version
|
||||
:libraries (->> (:libraries file) (into #{}) (mapv str))
|
||||
:exportType (d/name export-type)
|
||||
:hasComponents (d/not-empty? (get-in file [:data :components]))
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.file-builder :as fb]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as gpa]
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.text :as ct]
|
||||
|
@ -229,6 +231,21 @@
|
|||
(cond-> (= type :text)
|
||||
(d/update-when :content resolve-text-content context)))))
|
||||
|
||||
(defn- translate-frame
|
||||
[data type file]
|
||||
(let [frame-id (:current-frame-id file)
|
||||
frame (when (and (some? frame-id) (not= frame-id uuid/zero))
|
||||
(fb/lookup-shape file frame-id))]
|
||||
|
||||
(if (some? frame)
|
||||
(-> data
|
||||
(d/update-when :x + (:x frame))
|
||||
(d/update-when :y + (:y frame))
|
||||
(cond-> (= :path type)
|
||||
(update :content gpa/move-content (gpt/point (:x frame) (:y frame)))))
|
||||
|
||||
data)))
|
||||
|
||||
(defn process-import-node
|
||||
[context file node]
|
||||
|
||||
|
@ -250,7 +267,9 @@
|
|||
data (-> (cip/parse-data type node)
|
||||
(resolve-data-ids type context)
|
||||
(cond-> (some? old-id)
|
||||
(assoc :id (resolve old-id))))
|
||||
(assoc :id (resolve old-id)))
|
||||
(cond-> (< (:version context 1) 2)
|
||||
(translate-frame type file)))
|
||||
|
||||
file (case type
|
||||
:frame (fb/add-artboard file data)
|
||||
|
@ -463,6 +482,7 @@
|
|||
(rx/flat-map
|
||||
(fn [context]
|
||||
(->> (create-file context)
|
||||
(rx/tap #(.log js/console "create-file" (clj->js %)))
|
||||
(rx/map #(vector % (first (get data (:file-id context)))))))))
|
||||
|
||||
(->> (rx/from files)
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns debug
|
||||
(:import [goog.math AffineTransform])
|
||||
#_(:import [goog.math AffineTransform])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
#_[app.common.geom.matrix :as gmt]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.perf :as perf]
|
||||
#_[app.common.perf :as perf]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.store :as st]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as timers]
|
||||
|
@ -20,7 +21,32 @@
|
|||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center :export :import #_:simple-selection})
|
||||
(def debug-options
|
||||
#{;; Displays the bounding box for the shapes
|
||||
:bounding-boxes
|
||||
|
||||
;; Displays an overlay over the groups
|
||||
:group
|
||||
|
||||
;; Displays in the console log the events through the application
|
||||
:events
|
||||
|
||||
;; Display the boxes that represent the rotation handlers
|
||||
:rotation-handler
|
||||
|
||||
;; Display the boxes that represent the resize handlers
|
||||
:resize-handler
|
||||
|
||||
;; Displays the center of a selection
|
||||
:selection-center
|
||||
|
||||
;; When active the single selection will not take into account previous transformations
|
||||
;; this is useful to debug transforms
|
||||
:simple-selection
|
||||
|
||||
;; When active the thumbnails will be displayed with a sepia filter
|
||||
:thumbnails
|
||||
})
|
||||
|
||||
;; These events are excluded when we activate the :events flag
|
||||
(def debug-exclude-events
|
||||
|
@ -109,27 +135,40 @@
|
|||
(do-thing)))
|
||||
|
||||
(defn ^:export dump-state []
|
||||
(logjs "state" @st/state))
|
||||
(logjs "state" @st/state)
|
||||
nil)
|
||||
|
||||
(defn ^:export dump-buffer []
|
||||
(logjs "state" @st/last-events))
|
||||
(logjs "state" @st/last-events)
|
||||
nil)
|
||||
|
||||
(defn ^:export get-state [str-path]
|
||||
(let [path (->> (str/split str-path " ")
|
||||
(map d/read-string))]
|
||||
(clj->js (get-in @st/state path))))
|
||||
(clj->js (get-in @st/state path)))
|
||||
nil)
|
||||
|
||||
(defn ^:export dump-objects []
|
||||
(let [page-id (get @st/state :current-page-id)]
|
||||
(logjs "state" (get-in @st/state [:workspace-data :pages-index page-id :objects]))))
|
||||
(let [page-id (get @st/state :current-page-id)
|
||||
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])]
|
||||
(logjs "objects" objects)
|
||||
nil))
|
||||
|
||||
(defn ^:export dump-object [name]
|
||||
(let [page-id (get @st/state :current-page-id)
|
||||
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])
|
||||
target (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects)
|
||||
(get objects (uuid name)))]
|
||||
(->> target
|
||||
(logjs "state"))))
|
||||
result (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects)
|
||||
(get objects (uuid/uuid name)))]
|
||||
(logjs name result)
|
||||
nil))
|
||||
|
||||
(defn ^:export dump-selected []
|
||||
(let [page-id (get @st/state :current-page-id)
|
||||
objects (get-in @st/state [:workspace-data :pages-index page-id :objects])
|
||||
selected (get-in @st/state [:workspace-local :selected])
|
||||
result (->> selected (map (d/getf objects)))]
|
||||
(logjs "selected" result)
|
||||
nil))
|
||||
|
||||
(defn ^:export dump-tree
|
||||
([] (dump-tree false false))
|
||||
|
@ -212,7 +251,7 @@
|
|||
(not (debug-exclude-events (ptk/type s))))))
|
||||
(rx/subs #(println "[stream]: " (ptk/repr-event %))))))
|
||||
|
||||
(defn ^:export bench-matrix
|
||||
#_(defn ^:export bench-matrix
|
||||
[]
|
||||
(let [iterations 1000000
|
||||
|
||||
|
@ -247,3 +286,6 @@
|
|||
(println "Clojure matrix. Total: " m1 " (" (/ m1 iterations) ")")
|
||||
(println "Clojure matrix (NEW). Total: " m2 " (" (/ m2 iterations) ")")
|
||||
(println "Affine transform (with new). Total: " m3 " (" (/ m3 iterations) ")")))
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue