mirror of
https://github.com/penpot/penpot.git
synced 2025-03-14 08:41:48 -05:00
⚡ Performance improvements
This commit is contained in:
parent
814042909a
commit
b576ef02af
16 changed files with 510 additions and 315 deletions
|
@ -248,11 +248,20 @@
|
||||||
(dm/get-in data [:pages-index page-id])))
|
(dm/get-in data [:pages-index page-id])))
|
||||||
st/state))
|
st/state))
|
||||||
|
|
||||||
|
(defn workspace-page-objects-by-id
|
||||||
|
[page-id]
|
||||||
|
(l/derived #(wsh/lookup-page-objects % page-id) st/state =))
|
||||||
|
|
||||||
(def workspace-page-objects
|
(def workspace-page-objects
|
||||||
(l/derived wsh/lookup-page-objects st/state =))
|
(l/derived wsh/lookup-page-objects st/state =))
|
||||||
|
|
||||||
(def workspace-modifiers
|
(defn object-by-id
|
||||||
(l/derived :workspace-modifiers st/state))
|
[id]
|
||||||
|
(l/derived #(get % id) workspace-page-objects))
|
||||||
|
|
||||||
|
(defn objects-by-id
|
||||||
|
[ids]
|
||||||
|
(l/derived #(into [] (keep (d/getf %)) ids) workspace-page-objects =))
|
||||||
|
|
||||||
(def workspace-page-options
|
(def workspace-page-options
|
||||||
(l/derived :options workspace-page))
|
(l/derived :options workspace-page))
|
||||||
|
@ -266,13 +275,35 @@
|
||||||
(def workspace-editor-state
|
(def workspace-editor-state
|
||||||
(l/derived :workspace-editor-state st/state))
|
(l/derived :workspace-editor-state st/state))
|
||||||
|
|
||||||
(defn object-by-id
|
(def workspace-modifiers
|
||||||
[id]
|
(l/derived :workspace-modifiers st/state))
|
||||||
(l/derived #(get % id) workspace-page-objects))
|
|
||||||
|
|
||||||
(defn objects-by-id
|
(defn workspace-modifiers-by-id
|
||||||
[ids]
|
[ids]
|
||||||
(l/derived #(into [] (keep (d/getf %)) ids) workspace-page-objects))
|
(l/derived #(select-keys % ids) workspace-modifiers))
|
||||||
|
|
||||||
|
|
||||||
|
(def workspace-modifiers-with-objects
|
||||||
|
(l/derived
|
||||||
|
(fn [state]
|
||||||
|
{:modifiers (:workspace-modifiers state)
|
||||||
|
:objects (wsh/lookup-page-objects state)})
|
||||||
|
st/state
|
||||||
|
(fn [a b]
|
||||||
|
(and (= (:modifiers a) (:modifiers b))
|
||||||
|
(identical? (:objects a) (:objects b))))))
|
||||||
|
|
||||||
|
(defn workspace-modifiers-by-frame-id
|
||||||
|
[frame-id]
|
||||||
|
(l/derived
|
||||||
|
(fn [{:keys [modifiers objects]}]
|
||||||
|
(let [keys (->> modifiers
|
||||||
|
(keys)
|
||||||
|
(filter #(or (= frame-id %)
|
||||||
|
(= frame-id (get-in objects [% :frame-id])))))]
|
||||||
|
(select-keys modifiers keys)))
|
||||||
|
workspace-modifiers-with-objects
|
||||||
|
=))
|
||||||
|
|
||||||
(defn- set-content-modifiers [state]
|
(defn- set-content-modifiers [state]
|
||||||
(fn [id shape]
|
(fn [id shape]
|
||||||
|
|
|
@ -243,8 +243,9 @@
|
||||||
|
|
||||||
(let [shapes (->> shapes
|
(let [shapes (->> shapes
|
||||||
(remove cph/frame-shape?)
|
(remove cph/frame-shape?)
|
||||||
(mapcat #(cph/get-children-with-self objects (:id %))))]
|
(mapcat #(cph/get-children-with-self objects (:id %))))
|
||||||
[:& ff/fontfaces-style {:shapes shapes}])
|
fonts (ff/shapes->fonts shapes)]
|
||||||
|
[:& ff/fontfaces-style {:fonts fonts}])
|
||||||
|
|
||||||
(for [item shapes]
|
(for [item shapes]
|
||||||
(let [frame? (= (:type item) :frame)]
|
(let [frame? (= (:type item) :frame)]
|
||||||
|
@ -401,8 +402,8 @@
|
||||||
:style {:-webkit-print-color-adjust :exact}
|
:style {:-webkit-print-color-adjust :exact}
|
||||||
:fill "none"}
|
:fill "none"}
|
||||||
|
|
||||||
(let [shapes (cph/get-children objects object-id)]
|
(let [fonts (ff/frame->fonts obj-id objects)]
|
||||||
[:& ff/fontfaces-style {:shapes shapes}])
|
[:& ff/fontfaces-style {:fonts fonts}])
|
||||||
|
|
||||||
(case (:type object)
|
(case (:type object)
|
||||||
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
:frame [:& frame-wrapper {:shape object :view-box vbox}]
|
||||||
|
|
|
@ -213,6 +213,15 @@
|
||||||
(mf/set-ref-val! ref value)))
|
(mf/set-ref-val! ref value)))
|
||||||
(mf/ref-val ref)))
|
(mf/ref-val ref)))
|
||||||
|
|
||||||
|
(defn use-update-var
|
||||||
|
[value]
|
||||||
|
(let [ref (mf/use-var value)]
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps value)
|
||||||
|
(fn []
|
||||||
|
(reset! ref value)))
|
||||||
|
ref))
|
||||||
|
|
||||||
(defn use-equal-memo
|
(defn use-equal-memo
|
||||||
[val]
|
[val]
|
||||||
(let [ref (mf/use-ref nil)]
|
(let [ref (mf/use-ref nil)]
|
||||||
|
@ -248,3 +257,5 @@
|
||||||
(mf/deps focus objects)
|
(mf/deps focus objects)
|
||||||
#(cp/focus-objects objects focus))]
|
#(cp/focus-objects objects focus))]
|
||||||
objects)))
|
objects)))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,10 @@
|
||||||
:characterData true}
|
:characterData true}
|
||||||
mutation-obs (js/MutationObserver. on-mutation)]
|
mutation-obs (js/MutationObserver. on-mutation)]
|
||||||
(mf/set-ref-val! prev-obs-ref mutation-obs)
|
(mf/set-ref-val! prev-obs-ref mutation-obs)
|
||||||
(.observe mutation-obs node options))))))]
|
(.observe mutation-obs node options))))
|
||||||
|
|
||||||
|
;; Return node so it's more composable
|
||||||
|
node))]
|
||||||
|
|
||||||
(mf/with-effect
|
(mf/with-effect
|
||||||
(fn []
|
(fn []
|
||||||
|
|
|
@ -63,13 +63,14 @@
|
||||||
(let [childs (unchecked-get props "childs")
|
(let [childs (unchecked-get props "childs")
|
||||||
shape (unchecked-get props "shape")
|
shape (unchecked-get props "shape")
|
||||||
{:keys [x y width height]} shape
|
{:keys [x y width height]} shape
|
||||||
|
|
||||||
transform (gsh/transform-matrix shape)
|
transform (gsh/transform-matrix shape)
|
||||||
|
|
||||||
props (-> (attrs/extract-style-attrs shape)
|
props (-> (attrs/extract-style-attrs shape)
|
||||||
(obj/merge!
|
(obj/merge!
|
||||||
#js {:x x
|
#js {:x x
|
||||||
:y y
|
:y y
|
||||||
:transform transform
|
:transform (str transform)
|
||||||
:width width
|
:width width
|
||||||
:height height
|
:height height
|
||||||
:className "frame-background"}))
|
:className "frame-background"}))
|
||||||
|
|
|
@ -47,7 +47,6 @@
|
||||||
|
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
children (unchecked-get props "children")
|
children (unchecked-get props "children")
|
||||||
|
|
||||||
{:keys [x y width height]} shape
|
{:keys [x y width height]} shape
|
||||||
{:keys [attrs] :as content} (:content shape)
|
{:keys [attrs] :as content} (:content shape)
|
||||||
|
|
||||||
|
@ -61,7 +60,7 @@
|
||||||
(obj/set! "preserveAspectRatio" "none"))]
|
(obj/set! "preserveAspectRatio" "none"))]
|
||||||
|
|
||||||
[:& (mf/provider svg-ids-ctx) {:value ids-mapping}
|
[:& (mf/provider svg-ids-ctx) {:value ids-mapping}
|
||||||
[:g.svg-raw {:transform (gsh/transform-matrix shape)}
|
[:g.svg-raw {:transform (str (gsh/transform-matrix shape))}
|
||||||
[:> "svg" attrs children]]]))
|
[:> "svg" attrs children]]]))
|
||||||
|
|
||||||
(mf/defc svg-element
|
(mf/defc svg-element
|
||||||
|
|
|
@ -73,16 +73,25 @@
|
||||||
(when (d/not-empty? style)
|
(when (d/not-empty? style)
|
||||||
[:style style])))
|
[:style style])))
|
||||||
|
|
||||||
|
(defn frame->fonts
|
||||||
|
[frame objects]
|
||||||
|
(->> (cph/get-children objects (:id frame))
|
||||||
|
(filterv cph/text-shape?)
|
||||||
|
(mapv (comp fonts/get-content-fonts :content))
|
||||||
|
(reduce set/union #{})))
|
||||||
|
|
||||||
|
(defn shapes->fonts
|
||||||
|
[shapes]
|
||||||
|
(->> shapes
|
||||||
|
(filterv cph/text-shape?)
|
||||||
|
(mapv (comp fonts/get-content-fonts :content))
|
||||||
|
(reduce set/union #{})))
|
||||||
|
|
||||||
(mf/defc fontfaces-style
|
(mf/defc fontfaces-style
|
||||||
{::mf/wrap-props false
|
{::mf/wrap-props false
|
||||||
::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]}
|
::mf/wrap [#(mf/memo' % (mf/check-props ["fonts"]))]}
|
||||||
[props]
|
[props]
|
||||||
(let [;; Retrieve the fonts ids used by the text shapes
|
(let [;; Retrieve the fonts ids used by the text shapes
|
||||||
fonts (->> (obj/get props "shapes")
|
fonts (obj/get props "fonts")]
|
||||||
(filterv cph/text-shape?)
|
|
||||||
(mapv (comp fonts/get-content-fonts :content))
|
|
||||||
(reduce set/union #{})
|
|
||||||
(hooks/use-equal-memo))]
|
|
||||||
|
|
||||||
(when (d/not-empty? fonts)
|
(when (d/not-empty? fonts)
|
||||||
[:> fontfaces-style-render {:fonts fonts}])))
|
[:> fontfaces-style-render {:fonts fonts}])))
|
||||||
|
|
|
@ -67,9 +67,8 @@
|
||||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
opts #js {:shape shape}]
|
opts #js {:shape shape}]
|
||||||
|
|
||||||
(when (and (some? shape) (not (:hidden shape)))
|
(when (and (some? shape) (not (:hidden shape)))
|
||||||
[:*
|
[:*
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
|
|
|
@ -8,11 +8,17 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.colors :as cc]
|
[app.common.colors :as cc]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
|
[app.main.ui.shapes.embed :as embed]
|
||||||
[app.main.ui.shapes.frame :as frame]
|
[app.main.ui.shapes.frame :as frame]
|
||||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
[app.main.ui.shapes.text.fontfaces :as ff]
|
[app.main.ui.shapes.text.fontfaces :as ff]
|
||||||
|
[app.main.ui.workspace.viewport.utils :as utils]
|
||||||
|
[app.util.globals :as globals]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
@ -75,56 +81,268 @@
|
||||||
(mf/jsx component props mf/undefined)
|
(mf/jsx component props mf/undefined)
|
||||||
(mf/jsx frame-placeholder props mf/undefined)))))
|
(mf/jsx frame-placeholder props mf/undefined)))))
|
||||||
|
|
||||||
;; Draw the frame proper as a deferred component
|
(defn use-node-store
|
||||||
(defn deferred-frame-shape-factory
|
[thumbnail? node-ref rendered?]
|
||||||
|
|
||||||
|
(let [;; when `true` the node is in memory
|
||||||
|
in-memory? (mf/use-var nil)
|
||||||
|
|
||||||
|
;; State just for re-rendering
|
||||||
|
re-render (mf/use-state 0)
|
||||||
|
|
||||||
|
parent-ref (mf/use-var nil)
|
||||||
|
|
||||||
|
on-frame-load
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [node]
|
||||||
|
(when (and (some? node) (nil? @node-ref))
|
||||||
|
(let [content (.createElementNS globals/document "http://www.w3.org/2000/svg" "g")]
|
||||||
|
(.appendChild node content)
|
||||||
|
(reset! node-ref content)
|
||||||
|
(reset! parent-ref node)
|
||||||
|
(swap! re-render inc)))))]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps thumbnail?)
|
||||||
|
(fn []
|
||||||
|
(when (and (some? @parent-ref) (some? @node-ref) @rendered? thumbnail?)
|
||||||
|
(.removeChild @parent-ref @node-ref)
|
||||||
|
(reset! in-memory? true))
|
||||||
|
|
||||||
|
(when (and (some? @node-ref) @in-memory? (not thumbnail?))
|
||||||
|
(.appendChild @parent-ref @node-ref)
|
||||||
|
(reset! in-memory? false))))
|
||||||
|
|
||||||
|
on-frame-load))
|
||||||
|
|
||||||
|
(defn use-render-thumbnail
|
||||||
|
[{:keys [x y width height] :as shape} node-ref rendered? thumbnail? thumbnail-data]
|
||||||
|
|
||||||
|
(let [frame-canvas-ref (mf/use-ref nil)
|
||||||
|
frame-image-ref (mf/use-ref nil)
|
||||||
|
|
||||||
|
fixed-width (mth/clamp (:width shape) 250 2000)
|
||||||
|
fixed-height (/ (* (:height shape) fixed-width) (:width shape))
|
||||||
|
|
||||||
|
image-url (mf/use-state nil)
|
||||||
|
observer-ref (mf/use-var nil)
|
||||||
|
|
||||||
|
shape-ref (hooks/use-update-var shape)
|
||||||
|
|
||||||
|
on-image-load
|
||||||
|
(mf/use-callback
|
||||||
|
(fn []
|
||||||
|
(let [canvas-node (mf/ref-val frame-canvas-ref)
|
||||||
|
img-node (mf/ref-val frame-image-ref)
|
||||||
|
|
||||||
|
canvas-context (.getContext canvas-node "2d")
|
||||||
|
canvas-width (.-width canvas-node)
|
||||||
|
canvas-height (.-height canvas-node)]
|
||||||
|
(.clearRect canvas-context 0 0 canvas-width canvas-height)
|
||||||
|
(.rect canvas-context 0 0 canvas-width canvas-height)
|
||||||
|
(set! (.-fillStyle canvas-context) "#FFFFFF")
|
||||||
|
(.fill canvas-context)
|
||||||
|
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
|
||||||
|
|
||||||
|
(let [data (.toDataURL canvas-node "image/jpg" 1)]
|
||||||
|
(reset! thumbnail-data data))
|
||||||
|
(reset! image-url nil))))
|
||||||
|
|
||||||
|
on-change
|
||||||
|
(mf/use-callback
|
||||||
|
(fn []
|
||||||
|
(when (some? @node-ref)
|
||||||
|
(let [node @node-ref]
|
||||||
|
(ts/schedule-on-idle
|
||||||
|
#(let [frame-html (-> (js/XMLSerializer.)
|
||||||
|
(.serializeToString node))
|
||||||
|
|
||||||
|
{:keys [x y width height]} @shape-ref
|
||||||
|
svg-node (.createElementNS js/document "http://www.w3.org/2000/svg" "svg")
|
||||||
|
_ (.setAttribute svg-node "version" "1.1")
|
||||||
|
_ (.setAttribute svg-node "viewBox" (dm/str x " " y " " width " " height))
|
||||||
|
_ (.setAttribute svg-node "width" width)
|
||||||
|
_ (.setAttribute svg-node "height" height)
|
||||||
|
_ (unchecked-set svg-node "innerHTML" frame-html)
|
||||||
|
|
||||||
|
xml (-> (js/XMLSerializer.)
|
||||||
|
(.serializeToString svg-node)
|
||||||
|
js/encodeURIComponent
|
||||||
|
js/unescape
|
||||||
|
js/btoa)
|
||||||
|
|
||||||
|
img-src (str "data:image/svg+xml;base64," xml)]
|
||||||
|
(reset! image-url img-src)))))))
|
||||||
|
|
||||||
|
on-load-frame-dom
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [node]
|
||||||
|
(when (and (some? node) (nil? @observer-ref))
|
||||||
|
(let [observer (js/MutationObserver. on-change)]
|
||||||
|
(.observe observer node #js {:childList true :attributes true :characterData true :subtree true})
|
||||||
|
(reset! observer-ref observer)))
|
||||||
|
|
||||||
|
;; First time rendered if the thumbnail is not present we create it
|
||||||
|
(when (not thumbnail?) (on-change []))))]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(fn []
|
||||||
|
#(when (and (some? @node-ref) @rendered?)
|
||||||
|
(mf/unmount @node-ref)
|
||||||
|
(reset! node-ref nil)
|
||||||
|
(reset! rendered? false)
|
||||||
|
(when (some? @observer-ref)
|
||||||
|
(.disconnect @observer-ref)
|
||||||
|
(reset! observer-ref nil)))))
|
||||||
|
|
||||||
|
[on-load-frame-dom
|
||||||
|
(when (some? @image-url)
|
||||||
|
(mf/html
|
||||||
|
[:g.thumbnail-rendering
|
||||||
|
[:foreignObject {:opacity 0 :x x :y y :width width :height height}
|
||||||
|
[:canvas {:ref frame-canvas-ref
|
||||||
|
:width fixed-width
|
||||||
|
:height fixed-height}]]
|
||||||
|
|
||||||
|
[:image {:opacity 0
|
||||||
|
:ref frame-image-ref
|
||||||
|
:x (:x shape)
|
||||||
|
:y (:y shape)
|
||||||
|
:xlinkHref @image-url
|
||||||
|
:width (:width shape)
|
||||||
|
:height (:height shape)
|
||||||
|
:on-load on-image-load}]]))]))
|
||||||
|
|
||||||
|
(defn use-dynamic-modifiers
|
||||||
|
[shape objects node-ref]
|
||||||
|
|
||||||
|
(let [frame-modifiers-ref
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps (:id shape))
|
||||||
|
#(refs/workspace-modifiers-by-frame-id (:id shape)))
|
||||||
|
|
||||||
|
modifiers (mf/deref frame-modifiers-ref)
|
||||||
|
|
||||||
|
transforms
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps modifiers)
|
||||||
|
(fn []
|
||||||
|
(when (some? modifiers)
|
||||||
|
(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)))))
|
||||||
|
|
||||||
|
prev-shapes (mf/use-var nil)
|
||||||
|
prev-modifiers (mf/use-var nil)
|
||||||
|
prev-transforms (mf/use-var nil)]
|
||||||
|
|
||||||
|
(mf/use-layout-effect
|
||||||
|
(mf/deps transforms)
|
||||||
|
(fn []
|
||||||
|
(when (and (nil? @prev-transforms)
|
||||||
|
(some? transforms))
|
||||||
|
(utils/start-transform! @node-ref shapes))
|
||||||
|
|
||||||
|
(when (some? modifiers)
|
||||||
|
(utils/update-transform! @node-ref shapes transforms modifiers))
|
||||||
|
|
||||||
|
(when (and (some? @prev-modifiers)
|
||||||
|
(empty? modifiers))
|
||||||
|
(utils/remove-transform! @node-ref @prev-shapes))
|
||||||
|
|
||||||
|
(reset! prev-modifiers modifiers)
|
||||||
|
(reset! prev-transforms transforms)
|
||||||
|
(reset! prev-shapes shapes)))))
|
||||||
|
|
||||||
|
(defn frame-shape-factory-roots
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
|
|
||||||
(let [frame-shape (frame/frame-shape shape-wrapper)]
|
(let [frame-shape (frame/frame-shape shape-wrapper)]
|
||||||
(mf/fnc defered-frame-wrapper
|
(mf/fnc inner-frame-shape
|
||||||
{::mf/wrap-props false
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "childs" "fonts" "thumbnail?"]))]
|
||||||
::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "childs"]))
|
::mf/wrap-props false}
|
||||||
custom-deferred]}
|
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
childs (unchecked-get props "childs")]
|
childs (unchecked-get props "childs")
|
||||||
[:& frame-shape {:shape shape
|
thumbnail? (unchecked-get props "thumbnail?")
|
||||||
:childs childs}]))))
|
fonts (unchecked-get props "fonts")
|
||||||
|
objects (unchecked-get props "objects")
|
||||||
|
|
||||||
|
thumbnail-data (mf/use-state nil)
|
||||||
|
|
||||||
|
thumbnail? (and thumbnail?
|
||||||
|
(or (some? (:thumbnail shape))
|
||||||
|
(some? @thumbnail-data)))
|
||||||
|
|
||||||
|
|
||||||
|
;; References to the current rendered node and the its parentn
|
||||||
|
node-ref (mf/use-var nil)
|
||||||
|
|
||||||
|
;; when `true` we've called the mount for the frame
|
||||||
|
rendered? (mf/use-var false)
|
||||||
|
|
||||||
|
[on-load-frame-dom thumb-renderer]
|
||||||
|
(use-render-thumbnail shape node-ref rendered? thumbnail? thumbnail-data)
|
||||||
|
|
||||||
|
on-frame-load
|
||||||
|
(use-node-store thumbnail? node-ref rendered?)]
|
||||||
|
|
||||||
|
(use-dynamic-modifiers shape objects node-ref)
|
||||||
|
|
||||||
|
(when (and (some? @node-ref) (or @rendered? (not thumbnail?)))
|
||||||
|
(mf/mount
|
||||||
|
(mf/html
|
||||||
|
[:& (mf/provider embed/context) {:value true}
|
||||||
|
[:> shape-container #js {:shape shape :ref on-load-frame-dom}
|
||||||
|
[:& ff/fontfaces-style {:fonts fonts}]
|
||||||
|
[:> frame-shape {:shape shape
|
||||||
|
:childs childs} ]]])
|
||||||
|
@node-ref)
|
||||||
|
(when (not @rendered?) (reset! rendered? true)))
|
||||||
|
|
||||||
|
[:*
|
||||||
|
(when thumbnail?
|
||||||
|
[:> frame/frame-thumbnail {:shape (cond-> shape
|
||||||
|
(some? @thumbnail-data)
|
||||||
|
(assoc :thumbnail @thumbnail-data))}])
|
||||||
|
|
||||||
|
[:g.frame-container {:key "frame-container"
|
||||||
|
:ref on-frame-load}]
|
||||||
|
thumb-renderer]))))
|
||||||
|
|
||||||
(defn frame-wrapper-factory
|
(defn frame-wrapper-factory
|
||||||
[shape-wrapper]
|
[shape-wrapper]
|
||||||
(let [deferred-frame-shape (deferred-frame-shape-factory shape-wrapper)]
|
(let [frame-shape (frame-shape-factory-roots shape-wrapper)]
|
||||||
(mf/fnc frame-wrapper
|
(mf/fnc frame-wrapper
|
||||||
{::mf/wrap [#(mf/memo' % check-frame-props)]
|
{::mf/wrap [#(mf/memo' % check-frame-props)]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
|
|
||||||
(when-let [shape (unchecked-get props "shape")]
|
(let [shape (unchecked-get props "shape")
|
||||||
(let [objects (unchecked-get props "objects")
|
objects (unchecked-get props "objects")
|
||||||
thumbnail? (unchecked-get props "thumbnail?")
|
thumbnail? (unchecked-get props "thumbnail?")
|
||||||
|
|
||||||
children
|
children
|
||||||
(-> (mapv (d/getf objects) (:shapes shape))
|
(-> (mapv (d/getf objects) (:shapes shape))
|
||||||
(hooks/use-equal-memo))
|
(hooks/use-equal-memo))
|
||||||
|
|
||||||
all-children
|
fonts
|
||||||
(-> (cph/get-children objects (:id shape))
|
(-> (ff/frame->fonts shape objects)
|
||||||
(hooks/use-equal-memo))
|
(hooks/use-equal-memo))]
|
||||||
|
|
||||||
all-svg-text?
|
|
||||||
(mf/use-memo
|
|
||||||
(mf/deps all-children)
|
|
||||||
(fn []
|
|
||||||
(->> all-children
|
|
||||||
(filter #(and (= :text (:type %)) (not (:hidden %))))
|
|
||||||
(every? #(some? (:position-data %))))))
|
|
||||||
|
|
||||||
show-thumbnail? (and thumbnail? (some? (:thumbnail shape)) all-svg-text?)]
|
|
||||||
|
|
||||||
[:g.frame-wrapper {:display (when (:hidden shape) "none")}
|
|
||||||
[:> shape-container {:shape shape}
|
|
||||||
[:& ff/fontfaces-style {:shapes all-children}]
|
|
||||||
(if show-thumbnail?
|
|
||||||
[:& frame/frame-thumbnail {:shape shape}]
|
|
||||||
[:& deferred-frame-shape
|
|
||||||
{:shape shape
|
|
||||||
:childs children}])]])))))
|
|
||||||
|
|
||||||
|
[:g.frame-wrapper {:display (when (:hidden shape) "none")}
|
||||||
|
[:& frame-shape
|
||||||
|
{:key (str (:id shape))
|
||||||
|
:shape shape
|
||||||
|
:fonts fonts
|
||||||
|
:childs children
|
||||||
|
:objects objects
|
||||||
|
:thumbnail? thumbnail?}]]))))
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
||||||
childs (mf/deref childs-ref)]
|
childs (mf/deref childs-ref)]
|
||||||
|
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
[:& group-shape
|
[:& group-shape
|
||||||
|
|
|
@ -6,228 +6,23 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.shapes.text
|
(ns app.main.ui.workspace.shapes.text
|
||||||
(:require
|
(:require
|
||||||
[app.common.attrs :as attrs]
|
[app.common.data :as d]
|
||||||
[app.common.geom.matrix :as gmt]
|
|
||||||
[app.common.geom.shapes :as gsh]
|
|
||||||
[app.common.logging :as log]
|
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.main.data.workspace.changes :as dch]
|
|
||||||
[app.main.data.workspace.texts :as dwt]
|
|
||||||
[app.main.refs :as refs]
|
|
||||||
[app.main.store :as st]
|
|
||||||
[app.main.ui.hooks.mutable-observer :refer [use-mutable-observer]]
|
|
||||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
[app.main.ui.shapes.text.fo-text :as fo]
|
[app.main.ui.shapes.text :as text]
|
||||||
[app.main.ui.shapes.text.svg-text :as svg]
|
|
||||||
[app.util.dom :as dom]
|
|
||||||
[app.util.object :as obj]
|
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[app.util.text-editor :as ted]
|
|
||||||
[app.util.text-svg-position :as utp]
|
|
||||||
[app.util.timers :as timers]
|
|
||||||
[app.util.webapi :as wapi]
|
|
||||||
[beicon.core :as rx]
|
|
||||||
[debug :refer [debug?]]
|
[debug :refer [debug?]]
|
||||||
[okulary.core :as l]
|
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
;; Change this to :info :debug or :trace to debug this module
|
|
||||||
(log/set-level! :warn)
|
|
||||||
|
|
||||||
;; --- Text Wrapper for workspace
|
;; --- Text Wrapper for workspace
|
||||||
|
|
||||||
(mf/defc text-static-content
|
|
||||||
[{:keys [shape]}]
|
|
||||||
[:& fo/text-shape {:shape shape
|
|
||||||
:grow-type (:grow-type shape)}])
|
|
||||||
|
|
||||||
(defn- update-with-current-editor-state
|
|
||||||
[{:keys [id] :as shape}]
|
|
||||||
(let [editor-state-ref (mf/use-memo (mf/deps id) #(l/derived (l/key id) refs/workspace-editor-state))
|
|
||||||
editor-state (mf/deref editor-state-ref)
|
|
||||||
|
|
||||||
content (:content shape)
|
|
||||||
editor-content
|
|
||||||
(when editor-state
|
|
||||||
(-> editor-state
|
|
||||||
(ted/get-editor-current-content)
|
|
||||||
(ted/export-content)))]
|
|
||||||
|
|
||||||
(cond-> shape
|
|
||||||
(some? editor-content)
|
|
||||||
(assoc :content (attrs/merge content editor-content)))))
|
|
||||||
|
|
||||||
(mf/defc text-resize-content
|
|
||||||
{::mf/wrap-props false}
|
|
||||||
[props]
|
|
||||||
(let [{:keys [id name grow-type] :as shape} (obj/get props "shape")
|
|
||||||
|
|
||||||
;; NOTE: this breaks the hooks rule of "no hooks inside
|
|
||||||
;; conditional code"; but we ensure that this component will
|
|
||||||
;; not reused if edition flag is changed with `:key` prop.
|
|
||||||
;; Without the `:key` prop combining the shape-id and the
|
|
||||||
;; edition flag, this will result in a react error. This is
|
|
||||||
;; done for performance reason; with this change only the
|
|
||||||
;; shape with edition flag is watching the editor state ref.
|
|
||||||
shape (cond-> shape
|
|
||||||
(true? (obj/get props "edition?"))
|
|
||||||
(update-with-current-editor-state))
|
|
||||||
|
|
||||||
mnt (mf/use-ref true)
|
|
||||||
paragraph-ref (mf/use-state nil)
|
|
||||||
|
|
||||||
handle-resize-text
|
|
||||||
(mf/use-callback
|
|
||||||
(mf/deps id)
|
|
||||||
(fn [entries]
|
|
||||||
(when (seq entries)
|
|
||||||
;; RequestAnimationFrame so the "loop limit error" error is not thrown
|
|
||||||
;; https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
|
|
||||||
(timers/raf
|
|
||||||
#(let [width (obj/get-in entries [0 "contentRect" "width"])
|
|
||||||
height (obj/get-in entries [0 "contentRect" "height"])]
|
|
||||||
(when (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
|
|
||||||
(log/debug :msg "Resize detected" :shape-id id :width width :height height)
|
|
||||||
(st/emit! (dwt/resize-text id (mth/ceil width) (mth/ceil height)))))))))
|
|
||||||
|
|
||||||
text-ref-cb
|
|
||||||
(mf/use-callback
|
|
||||||
(mf/deps handle-resize-text)
|
|
||||||
(fn [node]
|
|
||||||
(when node
|
|
||||||
(timers/schedule
|
|
||||||
#(when (mf/ref-val mnt)
|
|
||||||
(when-let [ps-node (dom/query node ".paragraph-set")]
|
|
||||||
(reset! paragraph-ref ps-node)))))))]
|
|
||||||
|
|
||||||
(mf/use-effect
|
|
||||||
(mf/deps @paragraph-ref handle-resize-text grow-type)
|
|
||||||
(fn []
|
|
||||||
(when-let [paragraph-node @paragraph-ref]
|
|
||||||
(let [sub (->> (wapi/observe-resize paragraph-node)
|
|
||||||
(rx/observe-on :af)
|
|
||||||
(rx/subs handle-resize-text))]
|
|
||||||
(log/debug :msg "Attach resize observer" :shape-id id :shape-name name)
|
|
||||||
(fn []
|
|
||||||
(rx/dispose! sub))))))
|
|
||||||
|
|
||||||
(mf/use-effect
|
|
||||||
(fn [] #(mf/set-ref-val! mnt false)))
|
|
||||||
|
|
||||||
[:& fo/text-shape {:ref text-ref-cb
|
|
||||||
:shape shape
|
|
||||||
:grow-type (:grow-type shape)
|
|
||||||
:key (str "shape-" (:id shape))}]))
|
|
||||||
|
|
||||||
|
|
||||||
(mf/defc text-wrapper
|
(mf/defc text-wrapper
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [{:keys [id position-data] :as shape} (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")]
|
||||||
edition-ref (mf/use-memo (mf/deps id) #(l/derived (fn [o] (= id (:edition o))) refs/workspace-local))
|
|
||||||
edition? (mf/deref edition-ref)
|
|
||||||
|
|
||||||
local-position-data (mf/use-state nil)
|
|
||||||
|
|
||||||
sid-ref (mf/use-ref nil)
|
|
||||||
|
|
||||||
handle-change-foreign-object
|
|
||||||
(mf/use-callback
|
|
||||||
(fn [node]
|
|
||||||
(when-let [position-data (utp/calc-position-data node)]
|
|
||||||
(let [parent (dom/get-parent node)
|
|
||||||
parent-transform (dom/get-attribute parent "transform")
|
|
||||||
node-transform (dom/get-attribute node "transform")
|
|
||||||
|
|
||||||
parent-mtx (usvg/parse-transform parent-transform)
|
|
||||||
node-mtx (usvg/parse-transform node-transform)
|
|
||||||
|
|
||||||
;; We need to see what transformation is applied in the DOM to reverse it
|
|
||||||
;; before calculating the position data
|
|
||||||
mtx (-> (gmt/multiply parent-mtx node-mtx)
|
|
||||||
(gmt/inverse))
|
|
||||||
|
|
||||||
position-data
|
|
||||||
(->> position-data
|
|
||||||
(mapv #(merge % (-> (select-keys % [:x :y :width :height])
|
|
||||||
(gsh/transform-rect mtx)))))]
|
|
||||||
(reset! local-position-data position-data)))))
|
|
||||||
|
|
||||||
[node-ref on-change-node] (use-mutable-observer handle-change-foreign-object)
|
|
||||||
|
|
||||||
show-svg-text? (or (some? position-data) (some? @local-position-data))
|
|
||||||
|
|
||||||
shape
|
|
||||||
(cond-> shape
|
|
||||||
(some? @local-position-data)
|
|
||||||
(assoc :position-data @local-position-data))
|
|
||||||
|
|
||||||
update-position-data
|
|
||||||
(fn []
|
|
||||||
(when (some? @local-position-data)
|
|
||||||
(reset! local-position-data nil)
|
|
||||||
(st/emit! (dch/update-shapes
|
|
||||||
[id]
|
|
||||||
(fn [shape]
|
|
||||||
(-> shape
|
|
||||||
(assoc :position-data @local-position-data)))
|
|
||||||
{:save-undo? false}))))]
|
|
||||||
|
|
||||||
(mf/use-layout-effect
|
|
||||||
(mf/deps @local-position-data)
|
|
||||||
(fn []
|
|
||||||
;; Timer to update the shape. We do this so a lot of changes won't produce
|
|
||||||
;; a lot of updates (kind of a debounce)
|
|
||||||
(let [sid (timers/schedule 50 update-position-data)]
|
|
||||||
(fn []
|
|
||||||
(rx/dispose! sid)))))
|
|
||||||
|
|
||||||
(mf/use-layout-effect
|
|
||||||
(mf/deps show-svg-text?)
|
|
||||||
(fn []
|
|
||||||
(when-not show-svg-text?
|
|
||||||
;; There is no position data we need to calculate it even if no change has happened
|
|
||||||
;; this usualy happens the first time a text is rendered
|
|
||||||
(let [update-data
|
|
||||||
(fn update-data []
|
|
||||||
(let [node (mf/ref-val node-ref)]
|
|
||||||
(if (some? node)
|
|
||||||
(let [position-data (utp/calc-position-data node)]
|
|
||||||
(reset! local-position-data position-data))
|
|
||||||
|
|
||||||
;; No node present, we need to keep waiting
|
|
||||||
(do (when-let [sid (mf/ref-val sid-ref)] (rx/dispose! sid))
|
|
||||||
(when-not @local-position-data
|
|
||||||
(mf/set-ref-val! sid-ref (timers/schedule 100 update-data)))))))]
|
|
||||||
(mf/set-ref-val! sid-ref (timers/schedule 100 update-data))))
|
|
||||||
|
|
||||||
(fn []
|
|
||||||
(when-let [sid (mf/ref-val sid-ref)]
|
|
||||||
(rx/dispose! sid)))))
|
|
||||||
|
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
;; We keep hidden the shape when we're editing so it keeps track of the size
|
|
||||||
;; and updates the selrect accordingly
|
|
||||||
[:*
|
[:*
|
||||||
[:g.text-shape {:ref on-change-node
|
[:& text/text-shape {:shape shape}]
|
||||||
:opacity (when show-svg-text? 0)
|
|
||||||
:pointer-events "none"}
|
|
||||||
|
|
||||||
;; The `:key` prop here is mandatory because the
|
(when (and (debug? :text-outline) (d/not-empty? (:position-data shape)))
|
||||||
;; text-resize-content breaks a hooks rule and we can't reuse
|
|
||||||
;; the component if the edition flag changes.
|
|
||||||
[:& text-resize-content {:shape
|
|
||||||
(cond-> shape
|
|
||||||
show-svg-text?
|
|
||||||
(dissoc :transform :transform-inverse))
|
|
||||||
:edition? edition?
|
|
||||||
:key (str id edition?)}]]
|
|
||||||
|
|
||||||
(when show-svg-text?
|
|
||||||
[:g.text-svg {:pointer-events "none"}
|
|
||||||
[:& svg/text-shape {:shape shape}]])
|
|
||||||
|
|
||||||
(when (debug? :text-outline)
|
|
||||||
(for [data (:position-data shape)]
|
(for [data (:position-data shape)]
|
||||||
(let [{:keys [x y width height]} data]
|
(let [{:keys [x y width height]} data]
|
||||||
[:*
|
[:*
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.main.ui.workspace.shapes.text.viewport-texts
|
||||||
|
(:require
|
||||||
|
[app.common.attrs :as attrs]
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.common.pages.helpers :as cph]
|
||||||
|
[app.main.data.workspace.changes :as dch]
|
||||||
|
[app.main.data.workspace.texts :as dwt]
|
||||||
|
[app.main.refs :as refs]
|
||||||
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.hooks :as hooks]
|
||||||
|
[app.main.ui.shapes.text.fo-text :as fo]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.object :as obj]
|
||||||
|
[app.util.text-editor :as ted]
|
||||||
|
[app.util.text-svg-position :as utp]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(defn- update-with-editor-state
|
||||||
|
"Updates the shape with the current state in the editor"
|
||||||
|
[shape editor-state]
|
||||||
|
(let [content (:content shape)
|
||||||
|
editor-content
|
||||||
|
(when editor-state
|
||||||
|
(-> editor-state
|
||||||
|
(ted/get-editor-current-content)
|
||||||
|
(ted/export-content)))]
|
||||||
|
|
||||||
|
(cond-> shape
|
||||||
|
(some? editor-content)
|
||||||
|
(assoc :content (attrs/merge content editor-content)))))
|
||||||
|
|
||||||
|
(mf/defc text-container
|
||||||
|
{::mf/wrap-props false
|
||||||
|
::mf/wrap [mf/memo]}
|
||||||
|
[props]
|
||||||
|
(let [shape (obj/get props "shape")
|
||||||
|
|
||||||
|
handle-node-rendered
|
||||||
|
(fn [node]
|
||||||
|
(when node
|
||||||
|
;; Check if we need to update the size because it's auto-width or auto-height
|
||||||
|
(when (contains? #{:auto-height :auto-width} (:grow-type shape))
|
||||||
|
(let [{:keys [width height]}
|
||||||
|
(-> (dom/query node ".paragraph-set")
|
||||||
|
(dom/get-client-size))]
|
||||||
|
(when (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
|
||||||
|
(st/emit! (dwt/resize-text (:id shape) (mth/ceil width) (mth/ceil height))))))
|
||||||
|
|
||||||
|
;; Update the position-data of every text fragment
|
||||||
|
(let [position-data (utp/calc-position-data node)]
|
||||||
|
(st/emit! (dch/update-shapes
|
||||||
|
[(:id shape)]
|
||||||
|
(fn [shape]
|
||||||
|
(-> shape
|
||||||
|
(assoc :position-data position-data)))
|
||||||
|
{:save-undo? false})))))]
|
||||||
|
|
||||||
|
[:& fo/text-shape {:key (str "shape-" (:id shape))
|
||||||
|
:ref handle-node-rendered
|
||||||
|
:shape shape
|
||||||
|
:grow-type (:grow-type shape)}]))
|
||||||
|
|
||||||
|
(mf/defc viewport-texts
|
||||||
|
[{:keys [objects edition]}]
|
||||||
|
|
||||||
|
(let [editor-state (-> (mf/deref refs/workspace-editor-state)
|
||||||
|
(get edition))
|
||||||
|
|
||||||
|
text-shapes-ids
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps objects)
|
||||||
|
#(->> objects (vals) (filter cph/text-shape?) (map :id)))
|
||||||
|
|
||||||
|
text-shapes
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps text-shapes-ids editor-state edition)
|
||||||
|
#(cond-> (select-keys objects text-shapes-ids)
|
||||||
|
(some? editor-state)
|
||||||
|
(d/update-when edition update-with-editor-state editor-state)))
|
||||||
|
|
||||||
|
prev-text-shapes (hooks/use-previous text-shapes)
|
||||||
|
|
||||||
|
;; A change in position-data won't be a "real" change
|
||||||
|
text-change?
|
||||||
|
(fn [id]
|
||||||
|
(not= (-> (get text-shapes id)
|
||||||
|
(dissoc :position-data))
|
||||||
|
(-> (get prev-text-shapes id)
|
||||||
|
(dissoc :position-data))))
|
||||||
|
|
||||||
|
changed-texts
|
||||||
|
(->> (keys text-shapes)
|
||||||
|
(filter text-change?)
|
||||||
|
(map (d/getf text-shapes)))]
|
||||||
|
|
||||||
|
(for [{:keys [id] :as shape} changed-texts]
|
||||||
|
[:& text-container {:shape (dissoc shape :transform :transform-inverse)
|
||||||
|
:key (str (dm/str "text-container-" id))}])))
|
|
@ -275,21 +275,21 @@
|
||||||
:key id}])))]]))
|
:key id}])))]]))
|
||||||
|
|
||||||
(defn- strip-obj-data [obj]
|
(defn- strip-obj-data [obj]
|
||||||
(select-keys obj [:id
|
(dm/select-keys obj [:id
|
||||||
:name
|
:name
|
||||||
:blocked
|
:blocked
|
||||||
:hidden
|
:hidden
|
||||||
:shapes
|
:shapes
|
||||||
:type
|
:type
|
||||||
:content
|
:content
|
||||||
:parent-id
|
:parent-id
|
||||||
:component-id
|
:component-id
|
||||||
:component-file
|
:component-file
|
||||||
:shape-ref
|
:shape-ref
|
||||||
:touched
|
:touched
|
||||||
:metadata
|
:metadata
|
||||||
:masked-group?
|
:masked-group?
|
||||||
:bool-type]))
|
:bool-type]))
|
||||||
|
|
||||||
(defn- strip-objects
|
(defn- strip-objects
|
||||||
"Remove unnecesary data from objects map"
|
"Remove unnecesary data from objects map"
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.colors :as clr]
|
[app.common.colors :as clr]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
[app.main.ui.shapes.export :as use]
|
[app.main.ui.shapes.export :as use]
|
||||||
[app.main.ui.workspace.shapes :as shapes]
|
[app.main.ui.workspace.shapes :as shapes]
|
||||||
[app.main.ui.workspace.shapes.text.editor :as editor]
|
[app.main.ui.workspace.shapes.text.editor :as editor]
|
||||||
|
[app.main.ui.workspace.shapes.text.viewport-texts :as stv]
|
||||||
[app.main.ui.workspace.viewport.actions :as actions]
|
[app.main.ui.workspace.viewport.actions :as actions]
|
||||||
[app.main.ui.workspace.viewport.comments :as comments]
|
[app.main.ui.workspace.viewport.comments :as comments]
|
||||||
[app.main.ui.workspace.viewport.drawarea :as drawarea]
|
[app.main.ui.workspace.viewport.drawarea :as drawarea]
|
||||||
|
@ -33,7 +35,6 @@
|
||||||
[app.main.ui.workspace.viewport.selection :as selection]
|
[app.main.ui.workspace.viewport.selection :as selection]
|
||||||
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
|
||||||
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
[app.main.ui.workspace.viewport.snap-points :as snap-points]
|
||||||
[app.main.ui.workspace.viewport.thumbnail-renderer :as wtr]
|
|
||||||
[app.main.ui.workspace.viewport.utils :as utils]
|
[app.main.ui.workspace.viewport.utils :as utils]
|
||||||
[app.main.ui.workspace.viewport.widgets :as widgets]
|
[app.main.ui.workspace.viewport.widgets :as widgets]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
@ -67,9 +68,13 @@
|
||||||
drawing (mf/deref refs/workspace-drawing)
|
drawing (mf/deref refs/workspace-drawing)
|
||||||
options (mf/deref refs/workspace-page-options)
|
options (mf/deref refs/workspace-page-options)
|
||||||
focus (mf/deref refs/workspace-focus-selected)
|
focus (mf/deref refs/workspace-focus-selected)
|
||||||
base-objects (-> (mf/deref refs/workspace-page-objects)
|
|
||||||
|
objects-ref (mf/use-memo #(refs/workspace-page-objects-by-id page-id))
|
||||||
|
base-objects (-> (mf/deref objects-ref)
|
||||||
(ui-hooks/with-focus-objects focus))
|
(ui-hooks/with-focus-objects focus))
|
||||||
|
|
||||||
modifiers (mf/deref refs/workspace-modifiers)
|
modifiers (mf/deref refs/workspace-modifiers)
|
||||||
|
|
||||||
objects-modified (mf/with-memo [base-objects modifiers]
|
objects-modified (mf/with-memo [base-objects modifiers]
|
||||||
(gsh/merge-modifiers base-objects modifiers))
|
(gsh/merge-modifiers base-objects modifiers))
|
||||||
|
|
||||||
|
@ -176,15 +181,12 @@
|
||||||
(hooks/setup-keyboard alt? mod? space?)
|
(hooks/setup-keyboard alt? mod? space?)
|
||||||
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids @hover-disabled? focus zoom)
|
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids @hover-disabled? focus zoom)
|
||||||
(hooks/setup-viewport-modifiers modifiers base-objects)
|
(hooks/setup-viewport-modifiers modifiers base-objects)
|
||||||
|
|
||||||
(hooks/setup-shortcuts node-editing? drawing-path?)
|
(hooks/setup-shortcuts node-editing? drawing-path?)
|
||||||
(hooks/setup-active-frames base-objects vbox hover active-frames)
|
(hooks/setup-active-frames base-objects vbox hover active-frames zoom)
|
||||||
|
|
||||||
[:div.viewport
|
[:div.viewport
|
||||||
[:div.viewport-overlays {:ref overlays-ref}
|
[:div.viewport-overlays {:ref overlays-ref}
|
||||||
|
|
||||||
[:& wtr/frame-renderer {:objects base-objects
|
|
||||||
:background background}]
|
|
||||||
|
|
||||||
(when show-text-editor?
|
(when show-text-editor?
|
||||||
[:& editor/text-editor-viewport {:shape editing-shape
|
[:& editor/text-editor-viewport {:shape editing-shape
|
||||||
:viewport-ref viewport-ref
|
:viewport-ref viewport-ref
|
||||||
|
@ -230,6 +232,22 @@
|
||||||
:objects base-objects
|
:objects base-objects
|
||||||
:active-frames @active-frames}]]]]
|
:active-frames @active-frames}]]]]
|
||||||
|
|
||||||
|
[:svg.render-shapes
|
||||||
|
{:id "text-position-layer"
|
||||||
|
:xmlns "http://www.w3.org/2000/svg"
|
||||||
|
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||||
|
:preserveAspectRatio "xMidYMid meet"
|
||||||
|
:key (str "text-position-layer" page-id)
|
||||||
|
:width (:width vport 0)
|
||||||
|
:height (:height vport 0)
|
||||||
|
:view-box (utils/format-viewbox vbox)}
|
||||||
|
|
||||||
|
[:g {:pointer-events "none" :opacity 0}
|
||||||
|
[:& stv/viewport-texts {:key (dm/str "texts-" page-id)
|
||||||
|
:page-id page-id
|
||||||
|
:objects base-objects
|
||||||
|
:edition edition}]]]
|
||||||
|
|
||||||
[:svg.viewport-controls
|
[:svg.viewport-controls
|
||||||
{:xmlns "http://www.w3.org/2000/svg"
|
{:xmlns "http://www.w3.org/2000/svg"
|
||||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
[app.main.ui.workspace.viewport.utils :as utils]
|
[app.main.ui.workspace.viewport.utils :as utils]
|
||||||
[app.main.worker :as uw]
|
[app.main.worker :as uw]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
[app.util.globals :as globals]
|
||||||
[app.util.timers :as timers]
|
[app.util.timers :as timers]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[goog.events :as events]
|
[goog.events :as events]
|
||||||
|
@ -225,15 +226,15 @@
|
||||||
(fn []
|
(fn []
|
||||||
(when (and (nil? @prev-transforms)
|
(when (and (nil? @prev-transforms)
|
||||||
(some? transforms))
|
(some? transforms))
|
||||||
(utils/start-transform! shapes))
|
(utils/start-transform! globals/document shapes))
|
||||||
|
|
||||||
(when (some? modifiers)
|
(when (some? modifiers)
|
||||||
(utils/update-transform! shapes transforms modifiers))
|
(utils/update-transform! globals/document shapes transforms modifiers))
|
||||||
|
|
||||||
|
|
||||||
(when (and (some? @prev-modifiers)
|
(when (and (some? @prev-modifiers)
|
||||||
(not (some? modifiers)))
|
(not (some? modifiers)))
|
||||||
(utils/remove-transform! @prev-shapes))
|
(utils/remove-transform! globals/document @prev-shapes))
|
||||||
|
|
||||||
(reset! prev-modifiers modifiers)
|
(reset! prev-modifiers modifiers)
|
||||||
(reset! prev-transforms transforms)
|
(reset! prev-transforms transforms)
|
||||||
|
@ -246,7 +247,7 @@
|
||||||
(gsh/overlaps? frame vbox))))
|
(gsh/overlaps? frame vbox))))
|
||||||
|
|
||||||
(defn setup-active-frames
|
(defn setup-active-frames
|
||||||
[objects vbox hover active-frames]
|
[objects vbox hover active-frames zoom]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps vbox)
|
(mf/deps vbox)
|
||||||
|
@ -262,13 +263,16 @@
|
||||||
(reduce-kv set-active-frames {} active-frames))))))
|
(reduce-kv set-active-frames {} active-frames))))))
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps @hover @active-frames)
|
(mf/deps @hover @active-frames zoom)
|
||||||
(fn []
|
(fn []
|
||||||
(let [frame-id (if (= :frame (:type @hover))
|
(let [frame-id (if (= :frame (:type @hover))
|
||||||
(:id @hover)
|
(:id @hover)
|
||||||
(:frame-id @hover))]
|
(:frame-id @hover))]
|
||||||
(when (not (contains? @active-frames frame-id))
|
(if (< zoom 0.25)
|
||||||
(swap! active-frames assoc frame-id true))))))
|
(when (some? @active-frames)
|
||||||
|
(reset! active-frames nil))
|
||||||
|
(when (and (some? frame-id)(not (contains? @active-frames frame-id)))
|
||||||
|
(reset! active-frames {frame-id true})))))))
|
||||||
|
|
||||||
;; NOTE: this is executed on each page change, maybe we need to move
|
;; NOTE: this is executed on each page change, maybe we need to move
|
||||||
;; this shortcuts outside the viewport?
|
;; this shortcuts outside the viewport?
|
||||||
|
|
|
@ -77,8 +77,8 @@
|
||||||
|
|
||||||
(defn get-nodes
|
(defn get-nodes
|
||||||
"Retrieve the DOM nodes to apply the matrix transformation"
|
"Retrieve the DOM nodes to apply the matrix transformation"
|
||||||
[{:keys [id type masked-group?]}]
|
[base-node {:keys [id type masked-group?]}]
|
||||||
(let [shape-node (dom/get-element (str "shape-" id))
|
(let [shape-node (dom/query base-node (str "#shape-" id))
|
||||||
|
|
||||||
frame? (= :frame type)
|
frame? (= :frame type)
|
||||||
group? (= :group type)
|
group? (= :group type)
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
mask? (and group? masked-group?)
|
mask? (and group? masked-group?)
|
||||||
|
|
||||||
;; When the shape is a frame we maybe need to move its thumbnail
|
;; When the shape is a frame we maybe need to move its thumbnail
|
||||||
thumb-node (when frame? (dom/get-element (str "thumbnail-" id)))]
|
thumb-node (when frame? (dom/query base-node (str "#thumbnail-" id)))]
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
frame?
|
frame?
|
||||||
|
@ -132,9 +132,9 @@
|
||||||
(dom/set-attribute! node "height" height)))
|
(dom/set-attribute! node "height" height)))
|
||||||
|
|
||||||
(defn start-transform!
|
(defn start-transform!
|
||||||
[shapes]
|
[base-node shapes]
|
||||||
(doseq [shape shapes]
|
(doseq [shape shapes]
|
||||||
(when-let [nodes (get-nodes shape)]
|
(when-let [nodes (get-nodes base-node shape)]
|
||||||
(doseq [node nodes]
|
(doseq [node nodes]
|
||||||
(let [old-transform (dom/get-attribute node "transform")]
|
(let [old-transform (dom/get-attribute node "transform")]
|
||||||
(when (some? old-transform)
|
(when (some? old-transform)
|
||||||
|
@ -168,9 +168,9 @@
|
||||||
(dom/set-attribute! node att (str new-value))))
|
(dom/set-attribute! node att (str new-value))))
|
||||||
|
|
||||||
(defn update-transform!
|
(defn update-transform!
|
||||||
[shapes transforms modifiers]
|
[base-node shapes transforms modifiers]
|
||||||
(doseq [{:keys [id type] :as shape} shapes]
|
(doseq [{:keys [id type] :as shape} shapes]
|
||||||
(when-let [nodes (get-nodes shape)]
|
(when-let [nodes (get-nodes base-node shape)]
|
||||||
(let [transform (get transforms id)
|
(let [transform (get transforms id)
|
||||||
modifiers (get-in modifiers [id :modifiers])
|
modifiers (get-in modifiers [id :modifiers])
|
||||||
|
|
||||||
|
@ -214,9 +214,9 @@
|
||||||
(set-transform-att! node "transform" transform)))))))
|
(set-transform-att! node "transform" transform)))))))
|
||||||
|
|
||||||
(defn remove-transform!
|
(defn remove-transform!
|
||||||
[shapes]
|
[base-node shapes]
|
||||||
(doseq [shape shapes]
|
(doseq [shape shapes]
|
||||||
(when-let [nodes (get-nodes shape)]
|
(when-let [nodes (get-nodes base-node shape)]
|
||||||
(doseq [node nodes]
|
(doseq [node nodes]
|
||||||
(when (some? node)
|
(when (some? node)
|
||||||
(cond
|
(cond
|
||||||
|
|
Loading…
Add table
Reference in a new issue