Merge pull request #2182 from penpot/niwinz-viewer-performance

Viewer performance issues
This commit is contained in:
Andrés Moya 2022-08-23 14:01:25 +02:00 committed by GitHub
commit 4e319fd9ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 674 additions and 533 deletions

1
.gitignore vendored
View file

@ -23,6 +23,7 @@
/backend/resources/public/assets /backend/resources/public/assets
/backend/resources/public/media /backend/resources/public/media
/backend/target/ /backend/target/
/backend/builtin-templates
/bundle* /bundle*
/cd.md /cd.md
/clj-profiler/ /clj-profiler/

View file

@ -728,11 +728,16 @@
[data] [data]
(letfn [(process-map-form [form] (letfn [(process-map-form [form]
(cond-> form (cond-> form
;; Relink Image Shapes ;; Relink image shapes
(and (map? (:metadata form)) (and (map? (:metadata form))
(= :image (:type form))) (= :image (:type form)))
(update-in [:metadata :id] lookup-index) (update-in [:metadata :id] lookup-index)
;; Relink paths with fill image
(and (map? (:fill-image form))
(= :path (:type form)))
(update-in [:fill-image :id] lookup-index)
;; This covers old shapes and the new :fills. ;; This covers old shapes and the new :fills.
(uuid? (:fill-color-ref-file form)) (uuid? (:fill-color-ref-file form))
(update :fill-color-ref-file lookup-index) (update :fill-color-ref-file lookup-index)

View file

@ -40,6 +40,5 @@
(defn is-in-focus? (defn is-in-focus?
[objects focus id] [objects focus id]
(d/seek (d/seek (partial contains? focus)
#(contains? focus %) (cons id (cph/get-parent-ids objects id))))
(cph/get-parents-seq objects id)))

View file

@ -96,16 +96,6 @@
[objects id] [objects id]
(-> objects (get id) :parent-id)) (-> objects (get id) :parent-id))
(defn get-parents-seq
[objects shape-id]
(cond
(nil? shape-id)
nil
:else
(lazy-seq (cons shape-id (get-parents-seq objects (get-in objects [shape-id :parent-id]))))))
(defn get-parent-ids (defn get-parent-ids
"Returns a vector of parents of the specified shape." "Returns a vector of parents of the specified shape."
[objects shape-id] [objects shape-id]
@ -220,8 +210,8 @@
(defn- get-base (defn- get-base
[objects id-a id-b] [objects id-a id-b]
(let [parents-a (reverse (get-parents-seq objects id-a)) (let [parents-a (reverse (cons id-a (get-parent-ids objects id-a)))
parents-b (reverse (get-parents-seq objects id-b)) parents-b (reverse (cons id-b (get-parent-ids objects id-b)))
[base base-child-a base-child-b] [base base-child-a base-child-b]
(loop [parents-a (rest parents-a) (loop [parents-a (rest parents-a)
@ -648,7 +638,7 @@
(defn is-child? (defn is-child?
[objects parent-id candidate-child-id] [objects parent-id candidate-child-id]
(let [parents (get-parents-seq objects candidate-child-id)] (let [parents (get-parent-ids objects candidate-child-id)]
(some? (d/seek #(= % parent-id) parents)))) (some? (d/seek #(= % parent-id) parents))))
(defn reduce-objects (defn reduce-objects
@ -688,11 +678,10 @@
(defn get-shape-id-root-frame (defn get-shape-id-root-frame
[objects shape-id] [objects shape-id]
(->> (get-parents-seq objects shape-id) (->> (get-parent-ids objects shape-id)
(cons shape-id)
(map (d/getf objects)) (map (d/getf objects))
(d/seek #(and (= :frame (:type %)) (d/seek root-frame?)
(= uuid/zero (:frame-id %))))
:id)) :id))
(defn get-viewer-frames (defn get-viewer-frames

View file

@ -164,6 +164,7 @@
top: 50px; top: 50px;
width: 256px; width: 256px;
height: 100%; height: 100%;
z-index: 10;
} }
} }

View file

@ -342,6 +342,13 @@
(some? list) (some? list)
(assoc :list list))))))) (assoc :list list)))))))
(defn update-options
[params]
(ptk/reify ::update-options
ptk/UpdateEvent
(update [_ state]
(update state :comments-local merge params))))
(s/def ::create-draft-params (s/def ::create-draft-params
(s/keys :req-un [::page-id ::file-id ::position])) (s/keys :req-un [::page-id ::file-id ::position]))

View file

@ -7,6 +7,7 @@
(ns app.main.data.viewer (ns app.main.data.viewer
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
@ -20,6 +21,9 @@
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[potok.core :as ptk])) [potok.core :as ptk]))
(s/def ::nilable-boolean (s/nilable ::us/boolean))
(s/def ::nilable-animation (s/nilable ::ctsi/animation))
;; --- Local State Initialization ;; --- Local State Initialization
(def ^:private (def ^:private
@ -32,7 +36,6 @@
:comments-show :unresolved :comments-show :unresolved
:selected #{} :selected #{}
:collapsed #{} :collapsed #{}
:overlays []
:hover nil :hover nil
:share-id "" :share-id ""
:file-comments-users []}) :file-comments-users []})
@ -329,7 +332,8 @@
(declare flash-done) (declare flash-done)
(def flash-interactions (defn flash-interactions
[]
(ptk/reify ::flash-interactions (ptk/reify ::flash-interactions
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -367,7 +371,7 @@
(ptk/reify ::complete-animation (ptk/reify ::complete-animation
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(d/dissoc-in state [:viewer-local :current-animation])))) (dissoc state :viewer-animation))))
;; --- Navigation inside page ;; --- Navigation inside page
@ -376,7 +380,7 @@
(ptk/reify ::go-to-frame-by-index (ptk/reify ::go-to-frame-by-index
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:viewer-local :overlays] [])) (assoc state :viewer-overlays []))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -391,8 +395,9 @@
(go-to-frame frame-id nil)) (go-to-frame frame-id nil))
([frame-id animation] ([frame-id animation]
(us/verify ::us/uuid frame-id) (us/assert! ::us/uuid frame-id)
(us/verify (s/nilable ::ctsi/animation) animation) (us/assert! ::nilable-animation animation)
(ptk/reify ::go-to-frame (ptk/reify ::go-to-frame
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -404,10 +409,10 @@
frame (get frames index)] frame (get frames index)]
(cond-> state (cond-> state
:always :always
(assoc-in [:viewer-local :overlays] []) (assoc :viewer-overlays [])
(some? animation) (some? animation)
(assoc-in [:viewer-local :current-animation] (assoc :viewer-animation
{:kind :go-to-frame {:kind :go-to-frame
:orig-frame-id (:id frame) :orig-frame-id (:id frame)
:animation animation})))) :animation animation}))))
@ -440,7 +445,7 @@
(ptk/reify ::go-to-section (ptk/reify ::go-to-section
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:viewer-local :overlays] [])) (assoc state :viewer-overlays []))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@ -451,50 +456,53 @@
;; --- Overlays ;; --- Overlays
(defn- do-open-overlay (defn- open-overlay*
[state frame position close-click-outside background-overlay animation] [state frame position close-click-outside background-overlay animation]
(cond-> state (cond-> state
:always :always
(update-in [:viewer-local :overlays] conj (update :viewer-overlays conj
{:frame frame {:frame frame
:id (:id frame)
:position position :position position
:close-click-outside close-click-outside :close-click-outside close-click-outside
:background-overlay background-overlay}) :background-overlay background-overlay})
(some? animation) (some? animation)
(assoc-in [:viewer-local :current-animation] (assoc :viewer-animation
{:kind :open-overlay {:kind :open-overlay
:overlay-id (:id frame) :overlay-id (:id frame)
:animation animation}))) :animation animation})))
(defn- do-close-overlay (defn- close-overlay*
[state frame-id animation] [state frame-id animation]
(if (nil? animation) (if (nil? animation)
(update-in state [:viewer-local :overlays] (update state :viewer-overlays
(fn [overlays] (fn [overlays]
(d/removev #(= (:id (:frame %)) frame-id) overlays))) (d/removev #(= (:id (:frame %)) frame-id) overlays)))
(assoc-in state [:viewer-local :current-animation] (assoc state :viewer-animation
{:kind :close-overlay {:kind :close-overlay
:overlay-id frame-id :overlay-id frame-id
:animation animation}))) :animation animation})))
(defn open-overlay (defn open-overlay
[frame-id position close-click-outside background-overlay animation] [frame-id position close-click-outside background-overlay animation]
(us/verify ::us/uuid frame-id) (us/assert! ::us/uuid frame-id)
(us/verify ::gpt/point position) (us/assert! ::gpt/point position)
(us/verify (s/nilable ::us/boolean) close-click-outside) (us/assert! ::nilable-boolean close-click-outside)
(us/verify (s/nilable ::us/boolean) background-overlay) (us/assert! ::nilable-boolean background-overlay)
(us/verify (s/nilable ::ctsi/animation) animation) (us/assert! ::nilable-animation animation)
(ptk/reify ::open-overlay (ptk/reify ::open-overlay
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [route (:route state) (let [route (:route state)
qparams (:query-params route) qparams (:query-params route)
page-id (:page-id qparams) page-id (:page-id qparams)
frames (get-in state [:viewer :pages page-id :all-frames]) frames (dm/get-in state [:viewer :pages page-id :all-frames])
frame (d/seek #(= (:id %) frame-id) frames) frame (d/seek #(= (:id %) frame-id) frames)
overlays (get-in state [:viewer-local :overlays])] overlays (:viewer-overlays state)]
(if-not (some #(= (:frame %) frame) overlays) (if-not (some #(= (:frame %) frame) overlays)
(do-open-overlay state (open-overlay* state
frame frame
position position
close-click-outside close-click-outside
@ -502,13 +510,15 @@
animation) animation)
state))))) state)))))
(defn toggle-overlay (defn toggle-overlay
[frame-id position close-click-outside background-overlay animation] [frame-id position close-click-outside background-overlay animation]
(us/verify ::us/uuid frame-id) (us/assert! ::us/uuid frame-id)
(us/verify ::gpt/point position) (us/assert! ::gpt/point position)
(us/verify (s/nilable ::us/boolean) close-click-outside) (us/assert! ::nilable-boolean close-click-outside)
(us/verify (s/nilable ::us/boolean) background-overlay) (us/assert! ::nilable-boolean background-overlay)
(us/verify (s/nilable ::ctsi/animation) animation) (us/assert! ::nilable-animation animation)
(ptk/reify ::toggle-overlay (ptk/reify ::toggle-overlay
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -517,27 +527,28 @@
page-id (:page-id qparams) page-id (:page-id qparams)
frames (get-in state [:viewer :pages page-id :all-frames]) frames (get-in state [:viewer :pages page-id :all-frames])
frame (d/seek #(= (:id %) frame-id) frames) frame (d/seek #(= (:id %) frame-id) frames)
overlays (get-in state [:viewer-local :overlays])] overlays (:viewer-overlays state)]
(if-not (some #(= (:frame %) frame) overlays) (if-not (some #(= (:frame %) frame) overlays)
(do-open-overlay state (open-overlay* state
frame frame
position position
close-click-outside close-click-outside
background-overlay background-overlay
animation) animation)
(do-close-overlay state (close-overlay* state
(:id frame) (:id frame)
(ctsi/invert-direction animation))))))) (ctsi/invert-direction animation)))))))
(defn close-overlay (defn close-overlay
([frame-id] (close-overlay frame-id nil)) ([frame-id] (close-overlay frame-id nil))
([frame-id animation] ([frame-id animation]
(us/verify ::us/uuid frame-id) (us/assert! ::us/uuid frame-id)
(us/verify (s/nilable ::ctsi/animation) animation) (us/assert! ::nilable-animation animation)
(ptk/reify ::close-overlay (ptk/reify ::close-overlay
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(do-close-overlay state (close-overlay* state
frame-id frame-id
animation))))) animation)))))

View file

@ -29,6 +29,7 @@
(mf/defc on-main-error (mf/defc on-main-error
[{:keys [error] :as props}] [{:keys [error] :as props}]
(mf/with-effect (mf/with-effect
(js/console.log error)
(st/emit! (rt/assign-exception error))) (st/emit! (rt/assign-exception error)))
[:span "Internal application error"]) [:span "Internal application error"])

View file

@ -6,6 +6,7 @@
(ns app.main.ui.comments (ns app.main.ui.comments
(:require (:require
[app.common.geom.point :as gpt]
[app.config :as cfg] [app.config :as cfg]
[app.main.data.comments :as dcm] [app.main.data.comments :as dcm]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
@ -110,8 +111,10 @@
[:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]])])) [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]])]))
(mf/defc draft-thread (mf/defc draft-thread
[{:keys [draft zoom on-cancel on-submit] :as props}] [{:keys [draft zoom on-cancel on-submit position-modifier]}]
(let [position (:position draft) (let [position (cond-> (:position draft)
(some? position-modifier)
(gpt/transform position-modifier))
content (:content draft) content (:content draft)
pos-x (* (:x position) zoom) pos-x (* (:x position) zoom)
pos-y (* (:y position) zoom) pos-y (* (:y position) zoom)
@ -281,9 +284,12 @@
(l/derived (l/in [:comments id]) st/state)) (l/derived (l/in [:comments id]) st/state))
(mf/defc thread-comments (mf/defc thread-comments
[{:keys [thread zoom users origin]}] {::mf/wrap [mf/memo]}
[{:keys [thread zoom users origin position-modifier]}]
(let [ref (mf/use-ref) (let [ref (mf/use-ref)
pos (:position thread) pos (cond-> (:position thread)
(some? position-modifier)
(gpt/transform position-modifier))
pos-x (+ (* (:x pos) zoom) 14) pos-x (+ (* (:x pos) zoom) 14)
pos-y (- (* (:y pos) zoom) 14) pos-y (- (* (:y pos) zoom) 14)
@ -384,8 +390,11 @@
(mf/defc thread-bubble (mf/defc thread-bubble
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [thread zoom open? on-click origin]}] [{:keys [thread zoom open? on-click origin position-modifier]}]
(let [pos (:position thread) (let [pos (cond-> (:position thread)
(some? position-modifier)
(gpt/transform position-modifier))
drag? (mf/use-ref nil) drag? (mf/use-ref nil)
was-open? (mf/use-ref nil) was-open? (mf/use-ref nil)
@ -398,7 +407,8 @@
pos-x (* (or (:new-position-x @state) (:x pos)) zoom) pos-x (* (or (:new-position-x @state) (:x pos)) zoom)
pos-y (* (or (:new-position-y @state) (:y pos)) zoom) pos-y (* (or (:new-position-y @state) (:y pos)) zoom)
on-pointer-down* (mf/use-callback on-pointer-down*
(mf/use-callback
(mf/deps origin was-open? open? drag? on-pointer-down) (mf/deps origin was-open? open? drag? on-pointer-down)
(fn [event] (fn [event]
(when (not= origin :viewer) (when (not= origin :viewer)
@ -408,7 +418,8 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(on-pointer-down event)))) (on-pointer-down event))))
on-pointer-up* (mf/use-callback on-pointer-up*
(mf/use-callback
(mf/deps origin thread was-open? drag? on-pointer-up) (mf/deps origin thread was-open? drag? on-pointer-up)
(fn [event] (fn [event]
(when (not= origin :viewer) (when (not= origin :viewer)
@ -419,7 +430,8 @@
(and (not (mf/ref-val was-open?)) (not (mf/ref-val drag?)))) (and (not (mf/ref-val was-open?)) (not (mf/ref-val drag?))))
(st/emit! (dcm/open-thread thread)))))) (st/emit! (dcm/open-thread thread))))))
on-mouse-move* (mf/use-callback on-mouse-move*
(mf/use-callback
(mf/deps origin drag? on-mouse-move) (mf/deps origin drag? on-mouse-move)
(fn [event] (fn [event]
(when (not= origin :viewer) (when (not= origin :viewer)
@ -427,7 +439,8 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(on-mouse-move event)))) (on-mouse-move event))))
on-click* (mf/use-callback on-click*
(mf/use-callback
(mf/deps origin thread on-click) (mf/deps origin thread on-click)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
@ -448,7 +461,7 @@
[:span (:seqn thread)]])) [:span (:seqn thread)]]))
(mf/defc comment-thread (mf/defc comment-thread
[{:keys [item users on-click] :as props}] [{:keys [item users on-click]}]
(let [owner (get users (:owner-id item)) (let [owner (get users (:owner-id item))
on-click* on-click*
(mf/use-callback (mf/use-callback

View file

@ -8,12 +8,7 @@
(:require (:require
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def render-ctx (mf/create-context nil)) (def render-id (mf/create-context nil))
(def def-ctx (mf/create-context false))
;; This content is used to replace complex colors to simple ones
;; for text shapes in the export process
(def text-plain-colors-ctx (mf/create-context false))
(def current-route (mf/create-context nil)) (def current-route (mf/create-context nil))
(def current-profile (mf/create-context nil)) (def current-profile (mf/create-context nil))
@ -21,6 +16,8 @@
(def current-project-id (mf/create-context nil)) (def current-project-id (mf/create-context nil))
(def current-page-id (mf/create-context nil)) (def current-page-id (mf/create-context nil))
(def current-file-id (mf/create-context nil)) (def current-file-id (mf/create-context nil))
(def scroll-ctx (mf/create-context nil)) (def current-scroll (mf/create-context nil))
(def active-frames-ctx (mf/create-context nil)) (def current-zoom (mf/create-context nil))
(def active-frames (mf/create-context nil))
(def render-thumbnails (mf/create-context nil)) (def render-thumbnails (mf/create-context nil))

View file

@ -159,7 +159,7 @@
(defn add-style-attrs (defn add-style-attrs
([props shape] ([props shape]
(let [render-id (mf/use-ctx muc/render-ctx)] (let [render-id (mf/use-ctx muc/render-id)]
(add-style-attrs props shape render-id))) (add-style-attrs props shape render-id)))
([props shape render-id] ([props shape render-id]

View file

@ -211,7 +211,7 @@
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [render-id (mf/use-ctx muc/render-ctx) (let [render-id (mf/use-ctx muc/render-id)
child (obj/get props "children") child (obj/get props "children")
base-props (obj/get child "props") base-props (obj/get child "props")
elem-name (obj/get child "type") elem-name (obj/get child "type")
@ -253,7 +253,7 @@
(mf/defc inner-stroke (mf/defc inner-stroke
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [render-id (mf/use-ctx muc/render-ctx) (let [render-id (mf/use-ctx muc/render-id)
child (obj/get props "children") child (obj/get props "children")
base-props (obj/get child "props") base-props (obj/get child "props")
elem-name (obj/get child "type") elem-name (obj/get child "type")
@ -292,7 +292,7 @@
(let [child (obj/get props "children") (let [child (obj/get props "children")
shape (obj/get props "shape") shape (obj/get props "shape")
render-id (mf/use-ctx muc/render-ctx) render-id (mf/use-ctx muc/render-id)
index (obj/get props "index") index (obj/get props "index")
stroke-width (:stroke-width shape 0) stroke-width (:stroke-width shape 0)
stroke-style (:stroke-style shape :none) stroke-style (:stroke-style shape :none)
@ -417,7 +417,7 @@
shape (obj/get props "shape") shape (obj/get props "shape")
elem-name (obj/get child "type") elem-name (obj/get child "type")
position (or (obj/get props "position") 0) position (or (obj/get props "position") 0)
render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-ctx))] render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))]
[:g {:id (dm/fmt "fills-%" (:id shape))} [:g {:id (dm/fmt "fills-%" (:id shape))}
[:> elem-name (build-fill-props shape child position render-id)]])) [:> elem-name (build-fill-props shape child position render-id)]]))
@ -427,7 +427,7 @@
(let [child (obj/get props "children") (let [child (obj/get props "children")
shape (obj/get props "shape") shape (obj/get props "shape")
elem-name (obj/get child "type") elem-name (obj/get child "type")
render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-ctx)) render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))
stroke-id (dm/fmt "strokes-%" (:id shape)) stroke-id (dm/fmt "strokes-%" (:id shape))
stroke-props (-> (obj/create) stroke-props (-> (obj/create)
(obj/set! "id" stroke-id) (obj/set! "id" stroke-id)

View file

@ -286,7 +286,7 @@
(for [[index fill] (d/enumerate fills)] (for [[index fill] (d/enumerate fills)]
[:> "penpot:fill" [:> "penpot:fill"
#js {:penpot:fill-color (if (some? (:fill-color-gradient fill)) #js {:penpot:fill-color (if (some? (:fill-color-gradient fill))
(str/format "url(#%s)" (str "fill-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index)) (str/format "url(#%s)" (str "fill-color-gradient_" (mf/use-ctx muc/render-id) "_" index))
(d/name (:fill-color fill))) (d/name (:fill-color fill)))
:penpot:fill-color-ref-file (d/name (:fill-color-ref-file fill)) :penpot:fill-color-ref-file (d/name (:fill-color-ref-file fill))
:penpot:fill-color-ref-id (d/name (:fill-color-ref-id fill)) :penpot:fill-color-ref-id (d/name (:fill-color-ref-id fill))
@ -299,7 +299,7 @@
(for [[index stroke] (d/enumerate strokes)] (for [[index stroke] (d/enumerate strokes)]
[:> "penpot:stroke" [:> "penpot:stroke"
#js {:penpot:stroke-color (if (some? (:stroke-color-gradient stroke)) #js {:penpot:stroke-color (if (some? (:stroke-color-gradient stroke))
(str/format "url(#%s)" (str "stroke-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index)) (str/format "url(#%s)" (str "stroke-color-gradient_" (mf/use-ctx muc/render-id) "_" index))
(d/name (:stroke-color stroke))) (d/name (:stroke-color stroke)))
:penpot:stroke-color-ref-file (d/name (:stroke-color-ref-file stroke)) :penpot:stroke-color-ref-file (d/name (:stroke-color-ref-file stroke))
:penpot:stroke-color-ref-id (d/name (:stroke-color-ref-id stroke)) :penpot:stroke-color-ref-id (d/name (:stroke-color-ref-id stroke))

View file

@ -62,7 +62,7 @@
:height height :height height
:className "frame-background"})) :className "frame-background"}))
path? (some? (.-d props)) path? (some? (.-d props))
render-id (mf/use-ctx muc/render-ctx)] render-id (mf/use-ctx muc/render-id)]
[:* [:*
[:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))} [:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))}

View file

@ -104,7 +104,7 @@
(let [attr (obj/get props "attr") (let [attr (obj/get props "attr")
shape (obj/get props "shape") shape (obj/get props "shape")
id (obj/get props "id") id (obj/get props "id")
id' (mf/use-ctx muc/render-ctx) id' (mf/use-ctx muc/render-id)
id (or id (dm/str (name attr) "_" id')) id (or id (dm/str (name attr) "_" id'))
gradient (get shape attr) gradient (get shape attr)
gradient-props #js {:id id gradient-props #js {:id id

View file

@ -21,7 +21,7 @@
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs") childs (unchecked-get props "childs")
objects (unchecked-get props "objects") objects (unchecked-get props "objects")
render-id (mf/use-ctx muc/render-ctx) render-id (mf/use-ctx muc/render-id)
masked-group? (:masked-group? shape) masked-group? (:masked-group? shape)
[mask childs] (if masked-group? [mask childs] (if masked-group?

View file

@ -47,7 +47,7 @@
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [mask (unchecked-get props "mask") (let [mask (unchecked-get props "mask")
render-id (mf/use-ctx muc/render-ctx) render-id (mf/use-ctx muc/render-id)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask))) svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
;; This factory is generic, it's used for viewer, workspace and handoff. ;; This factory is generic, it's used for viewer, workspace and handoff.

View file

@ -9,8 +9,8 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.uuid :as uuid]
[app.main.ui.context :as muc] [app.main.ui.context :as muc]
[app.main.ui.hooks :as h]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.export :as ed] [app.main.ui.shapes.export :as ed]
[app.main.ui.shapes.fills :as fills] [app.main.ui.shapes.fills :as fills]
@ -49,14 +49,15 @@
{::mf/forward-ref true {::mf/forward-ref true
::mf/wrap-props false} ::mf/wrap-props false}
[props ref] [props ref]
(let [shape (obj/get props "shape")
children (obj/get props "children") (let [shape (unchecked-get props "shape")
pointer-events (obj/get props "pointer-events") children (unchecked-get props "children")
disable-shadows? (obj/get props "disable-shadows?") pointer-events (unchecked-get props "pointer-events")
disable-shadows? (unchecked-get props "disable-shadows?")
type (:type shape) type (:type shape)
render-id (mf/use-memo #(str (uuid/next))) render-id (h/use-id)
filter-id (str "filter_" render-id) filter-id (dm/str "filter_" render-id)
styles (-> (obj/create) styles (-> (obj/create)
(obj/set! "pointerEvents" pointer-events) (obj/set! "pointerEvents" pointer-events)
(cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal)) (cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal))
@ -91,7 +92,7 @@
svg-group? svg-group?
(propagate-wrapper-styles wrapper-props))] (propagate-wrapper-styles wrapper-props))]
[:& (mf/provider muc/render-ctx) {:value render-id} [:& (mf/provider muc/render-id) {:value render-id}
[:> :g wrapper-props [:> :g wrapper-props
(when include-metadata? (when include-metadata?
[:& ed/export-data {:shape shape}]) [:& ed/export-data {:shape shape}])

View file

@ -9,7 +9,6 @@
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.main.ui.context :as muc]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.text.styles :as sts] [app.main.ui.shapes.text.styles :as sts]
[app.util.color :as uc] [app.util.color :as uc]
@ -91,23 +90,6 @@
(recur (uc/next-rgb current-rgb)) (recur (uc/next-rgb current-rgb))
current-hex)))) current-hex))))
(defn- remap-colors
"Returns a new content replacing the original colors by their mapped 'simple color'"
[content color-mapping]
(cond-> content
(and (:fill-opacity content) (< (:fill-opacity content) 1.0))
(-> (assoc :fill-color (get color-mapping [(:fill-color content) (:fill-opacity content)]))
(assoc :fill-opacity 1.0))
(some? (:fill-color-gradient content))
(-> (assoc :fill-color (get color-mapping (:fill-color-gradient content)))
(assoc :fill-opacity 1.0)
(dissoc :fill-color-gradient))
(contains? content :children)
(update :children #(mapv (fn [node] (remap-colors node color-mapping)) %))))
(defn- fill->color (defn- fill->color
"Given a content node returns the information about that node fill color" "Given a content node returns the information about that node fill color"
[{:keys [fill-color fill-opacity fill-color-gradient]}] [{:keys [fill-color fill-opacity fill-color-gradient]}]
@ -199,13 +181,7 @@
;; We add 8px to add a padding for the exporter ;; We add 8px to add a padding for the exporter
;; width (+ width 8) ;; width (+ width 8)
[colors color-mapping color-mapping-inverse] (retrieve-colors shape) [colors _color-mapping color-mapping-inverse] (retrieve-colors shape)]
plain-colors? (mf/use-ctx muc/text-plain-colors-ctx)
content (cond-> content
plain-colors?
(remap-colors color-mapping))]
[:foreignObject [:foreignObject
{:x x {:x x

View file

@ -53,7 +53,7 @@
::mf/wrap [mf/memo]} ::mf/wrap [mf/memo]}
[props] [props]
(let [render-id (mf/use-ctx muc/render-ctx) (let [render-id (mf/use-ctx muc/render-id)
shape (obj/get props "shape") shape (obj/get props "shape")
shape (cond-> shape (:is-mask? shape) set-white-fill) shape (cond-> shape (:is-mask? shape) set-white-fill)
@ -106,6 +106,6 @@
(obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))}) (obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))})
shape (assoc shape :fills (:fills data))] shape (assoc shape :fills (:fills data))]
[:& (mf/provider muc/render-ctx) {:key index :value (str render-id "_" (:id shape) "_" index)} [:& (mf/provider muc/render-id) {:key index :value (str render-id "_" (:id shape) "_" index)}
[:& shape-custom-strokes {:shape shape :position index :render-id render-id} [:& shape-custom-strokes {:shape shape :position index :render-id render-id}
[:> :text props (:text data)]]]))]])) [:> :text props (:text data)]]]))]]))

View file

@ -27,7 +27,7 @@
[app.main.ui.static :as static] [app.main.ui.static :as static]
[app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]] [app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]]
[app.main.ui.viewer.handoff :as handoff] [app.main.ui.viewer.handoff :as handoff]
[app.main.ui.viewer.header :refer [header]] [app.main.ui.viewer.header :as header]
[app.main.ui.viewer.interactions :as interactions] [app.main.ui.viewer.interactions :as interactions]
[app.main.ui.viewer.login] [app.main.ui.viewer.login]
[app.main.ui.viewer.thumbnails :refer [thumbnails-panel]] [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
@ -36,8 +36,15 @@
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[cuerdas.core :as str] [cuerdas.core :as str]
[goog.events :as events] [goog.events :as events]
[okulary.core :as l]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def current-animation-ref
(l/derived :viewer-animation st/state))
(def current-overlays-ref
(l/derived :viewer-overlays st/state))
(defn- calculate-size (defn- calculate-size
[objects frame zoom] [objects frame zoom]
(let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)] (let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)]
@ -60,7 +67,6 @@
:height (* height zoom) :height (* height zoom)
:vbox (str "0 0 " width " " height)}))) :vbox (str "0 0 " width " " height)})))
(mf/defc viewer-pagination (mf/defc viewer-pagination
[{:keys [index num-frames left-bar right-bar] :as props}] [{:keys [index num-frames left-bar right-bar] :as props}]
[:* [:*
@ -75,23 +81,83 @@
[:div.counter (str/join " / " [(+ index 1) num-frames])] [:div.counter (str/join " / " [(+ index 1) num-frames])]
[:span]]]) [:span]]])
(mf/defc viewer-wrapper (mf/defc viewer-pagination-and-sidebar
[{:keys [wrapper-size scroll orig-frame orig-viewport-ref orig-size page file users current-viewport-ref {::mf/wrap [mf/memo]}
size frame interactions-mode overlays zoom close-overlay section index] :as props}] [{:keys [section index users frame page]}]
(let [{clist :list} (mf/deref refs/comments-local) (let [comments-local (mf/deref refs/comments-local)
show-comments-list (and (= section :comments) (= :show clist))] show-sidebar? (and (= section :comments) (:show-sidebar? comments-local))]
[:* [:*
[:& viewer-pagination {:index index :num-frames (count (:frames page)) :right-bar show-comments-list}] [:& viewer-pagination
{:index index
:num-frames (count (:frames page))
:right-bar show-sidebar?}]
(when show-comments-list (when show-sidebar?
[:& comments-sidebar {:users users :frame frame :page page}]) [:& comments-sidebar
{:users users
:frame frame
:page page}])]))
(mf/defc viewer-overlay
[{:keys [overlay page frame zoom wrapper-size close-overlay interactions-mode]}]
(let [close-click-outside? (:close-click-outside overlay)
background-overlay? (:background-overlay overlay)
overlay-frame (:frame overlay)
overlay-position (:position overlay)
size
(mf/with-memo [page overlay zoom]
(calculate-size (:objects page) (:frame overlay) zoom))
on-click
(mf/use-fn
(mf/deps overlay close-overlay close-click-outside?)
(fn [_]
(when close-click-outside?
(close-overlay (:frame overlay)))))]
[:*
(when (or close-click-outside? background-overlay?)
[:div.viewer-overlay-background
{:class (dom/classnames :visible background-overlay?)
:style {:width (:width wrapper-size)
:height (:height wrapper-size)
:position "absolute"
:left 0
:top 0}
:on-click on-click}])
[:div.viewport-container.viewer-overlay
{:id (dm/str "overlay-" (:id overlay-frame))
:style {:width (:width size)
:height (:height size)
:left (* (:x overlay-position) zoom)
:top (* (:y overlay-position) zoom)}}
[:& interactions/viewport
{:frame overlay-frame
:base-frame frame
:frame-offset overlay-position
:size size
:page page
:interactions-mode interactions-mode}]]]))
(mf/defc viewer-wrapper
[{:keys [wrapper-size orig-frame orig-viewport-ref orig-size page file users current-viewport-ref
size frame interactions-mode overlays zoom close-overlay section index] :as props}]
[:*
[:& viewer-pagination-and-sidebar
{:section section
:index index
:page page
:users users
:frame frame}]
[:div.viewer-wrapper [:div.viewer-wrapper
{:style {:width (:width wrapper-size) {:style {:width (:width wrapper-size)
:height (:height wrapper-size)}} :height (:height wrapper-size)}}
[:& (mf/provider ctx/scroll-ctx) {:value @scroll}
[:div.viewer-clipper [:div.viewer-clipper
[:*
(when orig-frame (when orig-frame
[:div.viewport-container [:div.viewport-container
{:ref orig-viewport-ref {:ref orig-viewport-ref
@ -105,7 +171,6 @@
:frame-offset (gpt/point 0 0) :frame-offset (gpt/point 0 0)
:size orig-size :size orig-size
:page page :page page
:file file
:users users :users users
:interactions-mode :hide}]]) :interactions-mode :hide}]])
@ -121,48 +186,27 @@
:frame-offset (gpt/point 0 0) :frame-offset (gpt/point 0 0)
:size size :size size
:page page :page page
:file file
:users users
:interactions-mode interactions-mode}] :interactions-mode interactions-mode}]
(for [overlay overlays] (for [overlay overlays]
(let [size-over (calculate-size (:objects page) (:frame overlay) zoom)] [:& viewer-overlay {:overlay overlay
[:* :key (dm/str (:id overlay))
(when (or (:close-click-outside overlay)
(:background-overlay overlay))
[:div.viewer-overlay-background
{:class (dom/classnames
:visible (:background-overlay overlay))
:style {:width (:width wrapper-size)
:height (:height wrapper-size)
:position "absolute"
:left 0
:top 0}
:on-click #(when (:close-click-outside overlay)
(close-overlay (:frame overlay)))}])
[:div.viewport-container.viewer-overlay
{:id (str "overlay-" (-> overlay :frame :id))
:style {:width (:width size-over)
:height (:height size-over)
:left (* (:x (:position overlay)) zoom)
:top (* (:y (:position overlay)) zoom)}}
[:& interactions/viewport
{:frame (:frame overlay)
:base-frame frame
:frame-offset (:position overlay)
:size size-over
:page page :page page
:file file :frame frame
:users users :zoom zoom
:interactions-mode interactions-mode}]]]))]] :wrapper-size wrapper-size
:close-overlay close-overlay
:interactions-mode interactions-mode}])
]]
(when (= section :comments) (when (= section :comments)
[:& comments-layer {:file file [:& comments-layer {:file file
:users users :users users
:frame frame :frame frame
:page page :page page
:zoom zoom}])]]]])) :zoom zoom}])]])
(mf/defc viewer (mf/defc viewer
[{:keys [params data]}] [{:keys [params data]}]
@ -187,7 +231,8 @@
orig-viewport-ref (mf/use-ref nil) orig-viewport-ref (mf/use-ref nil)
current-viewport-ref (mf/use-ref nil) current-viewport-ref (mf/use-ref nil)
viewer-section-ref (mf/use-ref nil) viewer-section-ref (mf/use-ref nil)
current-animation (:current-animation local)
current-animation (mf/deref current-animation-ref)
page-id (or page-id (-> file :data :pages first)) page-id (or page-id (-> file :data :pages first))
@ -206,47 +251,48 @@
frames (:frames page) frames (:frames page)
frame (get frames index) frame (get frames index)
fullscreen? (mf/deref refs/viewer-fullscreen?) fullscreen? (mf/deref header/fullscreen-ref)
overlays (:overlays local) overlays (mf/deref current-overlays-ref)
scroll (mf/use-state nil) scroll (mf/use-state nil)
orig-frame orig-frame
(when (:orig-frame-id current-animation) (when (:orig-frame-id current-animation)
(d/seek #(= (:id %) (:orig-frame-id current-animation)) frames)) (d/seek #(= (:id %) (:orig-frame-id current-animation)) frames))
size (mf/use-memo size
(mf/deps frame zoom) (mf/with-memo [frame zoom]
(fn [] (calculate-size (:objects page) frame zoom))) (calculate-size (:objects page) frame zoom))
orig-size (mf/use-memo orig-size
(mf/deps orig-frame zoom) (mf/with-memo [orig-frame zoom]
(fn [] (when orig-frame (when orig-frame
(calculate-size (:objects page) orig-frame zoom)))) (calculate-size (:objects page) orig-frame zoom)))
wrapper-size (mf/use-memo wrapper-size
(mf/deps size orig-size zoom) (mf/with-memo [size orig-size zoom]
(fn [] (calculate-wrapper size orig-size zoom))) (calculate-wrapper size orig-size zoom))
interactions-mode interactions-mode
(:interactions-mode local) (:interactions-mode local)
on-click on-click
(mf/use-callback (mf/use-fn
(mf/deps section) (mf/deps section)
(fn [_] (fn [_]
(when (= section :comments) (when (= section :comments)
(st/emit! (dcm/close-thread))))) (st/emit! (dcm/close-thread)))))
set-up-new-size set-up-new-size
(mf/use-callback (mf/use-fn
(fn [_] (fn [_]
(let [viewer-section (dom/get-element "viewer-section") (let [viewer-section (dom/get-element "viewer-section")
size (dom/get-client-size viewer-section)] size (dom/get-client-size viewer-section)]
(st/emit! (dv/set-viewport-size {:size size}))))) (st/emit! (dv/set-viewport-size {:size size})))))
on-scroll on-scroll
(mf/use-fn
(fn [event] (fn [event]
(reset! scroll (dom/get-target-scroll event)))] (reset! scroll (dom/get-target-scroll event))))]
(hooks/use-shortcuts ::viewer sc/shortcuts) (hooks/use-shortcuts ::viewer sc/shortcuts)
@ -264,14 +310,13 @@
(let [name (:name file)] (let [name (:name file)]
(dom/set-html-title (str "\u25b6 " (tr "title.viewer" name)))))) (dom/set-html-title (str "\u25b6 " (tr "title.viewer" name))))))
(mf/use-effect (mf/with-effect []
(fn []
(dom/set-html-theme-color clr/gray-50 "dark") (dom/set-html-theme-color clr/gray-50 "dark")
(let [key1 (events/listen js/window "click" on-click) (let [key1 (events/listen js/window "click" on-click)
key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)] key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)]
(fn [] (fn []
(events/unlistenByKey key1) (events/unlistenByKey key1)
(events/unlistenByKey key2))))) (events/unlistenByKey key2))))
(mf/use-layout-effect (mf/use-layout-effect
(fn [] (fn []
@ -362,13 +407,15 @@
fonts (into #{} (keep :font-id) text-nodes)] fonts (into #{} (keep :font-id) text-nodes)]
(run! fonts/ensure-loaded! fonts)))) (run! fonts/ensure-loaded! fonts))))
[:div#viewer-layout {:class (dom/classnames [:div#viewer-layout
{:class (dom/classnames
:force-visible (:show-thumbnails local) :force-visible (:show-thumbnails local)
:viewer-layout (not= section :handoff) :viewer-layout (not= section :handoff)
:handoff-layout (= section :handoff) :handoff-layout (= section :handoff)
:fullscreen fullscreen?)} :fullscreen fullscreen?)}
[:& header {:project project [:& header/header
{:project project
:index index :index index
:file file :file file
:page page :page page
@ -410,9 +457,10 @@
:index index :index index
:viewer-pagination viewer-pagination}] :viewer-pagination viewer-pagination}]
[:& (mf/provider ctx/current-scroll) {:value @scroll}
[:& (mf/provider ctx/current-zoom) {:value zoom}
[:& viewer-wrapper [:& viewer-wrapper
{:wrapper-size wrapper-size {:wrapper-size wrapper-size
:scroll scroll
:orig-frame orig-frame :orig-frame orig-frame
:orig-viewport-ref orig-viewport-ref :orig-viewport-ref orig-viewport-ref
:orig-size orig-size :orig-size orig-size
@ -426,7 +474,7 @@
:overlays overlays :overlays overlays
:zoom zoom :zoom zoom
:section section :section section
:index index}]))]]])) :index index}]]]))]]]))
;; --- Component: Viewer Page ;; --- Component: Viewer Page

View file

@ -6,6 +6,7 @@
(ns app.main.ui.viewer.comments (ns app.main.ui.viewer.comments
(:require (:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
@ -23,132 +24,145 @@
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(mf/defc comments-menu (mf/defc comments-menu
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[] []
(let [{cmode :mode cshow :show clist :list} (mf/deref refs/comments-local) (let [local (mf/deref refs/comments-local)
owner-filter (:owner-filter local)
status-filter (:status-filter local)
show-sidebar? (:show-sidebar? local)
show-dropdown? (mf/use-state false) show-dropdown? (mf/use-state false)
toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
update-mode update-option (mf/use-fn
(mf/use-callback (fn [event]
(fn [mode] (let [target (dom/get-current-target event)
(st/emit! (dcm/update-filters {:mode mode})))) key (d/read-string (dom/get-attribute target "data-key"))
val (d/read-string (dom/get-attribute target "data-val"))]
update-show (st/emit! (dcm/update-options {key val})))))]
(mf/use-callback
(fn [mode]
(st/emit! (dcm/update-filters {:show mode}))))
update-list
(mf/use-callback
(fn [show-list]
(st/emit! (dcm/update-filters {:list show-list}))))]
[:div.view-options {:on-click toggle-dropdown} [:div.view-options {:on-click toggle-dropdown}
[:span.label (tr "labels.comments")] [:span.label (tr "labels.comments")]
[:span.icon i/arrow-down] [:span.icon i/arrow-down]
[:& dropdown {:show @show-dropdown? [:& dropdown {:show @show-dropdown?
:on-close hide-dropdown} :on-close hide-dropdown}
[:ul.dropdown.with-check [:ul.dropdown.with-check
[:li {:class (dom/classnames :selected (= :all cmode)) [:li {:class (dom/classnames :selected (= :all owner-filter))
:on-click #(update-mode :all)} :data-key ":owner-filter"
:data-val ":all"
:on-click update-option}
[:span.icon i/tick] [:span.icon i/tick]
[:span.label (tr "labels.show-all-comments")]] [:span.label (tr "labels.show-all-comments")]]
[:li {:class (dom/classnames :selected (= :yours cmode)) [:li {:class (dom/classnames :selected (= :yours owner-filter))
:on-click #(update-mode :yours)} :data-key ":owner-filter"
:data-val ":yours"
:on-click update-option}
[:span.icon i/tick] [:span.icon i/tick]
[:span.label (tr "labels.show-your-comments")]] [:span.label (tr "labels.show-your-comments")]]
[:hr] [:hr]
[:li {:class (dom/classnames :selected (= :pending cshow)) [:li {:class (dom/classnames :selected (= :pending status-filter))
:on-click #(update-show (if (= :pending cshow) :all :pending))} :data-key ":status-filter"
:data-val (if (= :pending status-filter) ":all" ":pending")
:on-click update-option}
[:span.icon i/tick] [:span.icon i/tick]
[:span.label (tr "labels.hide-resolved-comments")]] [:span.label (tr "labels.hide-resolved-comments")]]
[:hr] [:hr]
[:li {:class (dom/classnames :selected show-sidebar?)
[:li {:class (dom/classnames :selected (= :show clist)) :data-key ":show-sidebar?"
:on-click #(update-list (if (= :show clist) :hide :show))} :data-val (if show-sidebar? "false" "true")
:on-click update-option}
[:span.icon i/tick] [:span.icon i/tick]
[:span.label (tr "labels.show-comments-list")]]]]])) [:span.label (tr "labels.show-comments-list")]]]]]))
(def threads-ref (defn- update-thread-position [positions {:keys [id] :as thread}]
(l/derived :comment-threads st/state)) (if-let [data (get positions id)]
(-> thread
(def comments-local-ref (assoc :position (:position data))
(l/derived :comments-local st/state)) (assoc :frame-id (:frame-id data)))
thread))
(mf/defc comments-layer (mf/defc comments-layer
[{:keys [zoom file users frame page] :as props}] [{:keys [zoom file users frame page] :as props}]
(prn "comments-layer")
(let [profile (mf/deref refs/profile) (let [profile (mf/deref refs/profile)
threads-position-ref (l/derived (l/in [:viewer :pages (:id page) :options :comment-threads-position]) st/state) local (mf/deref refs/comments-local)
threads-position-map (mf/deref threads-position-ref)
threads-map (mf/deref threads-ref)
frame-corner (-> frame :points gsh/points->selrect gpt/point) open-thread-id (:open local)
modifier1 (-> (gmt/matrix) page-id (:id page)
(gmt/translate (gpt/negate frame-corner))) file-id (:id file)
frame-id (:id frame)
modifier2 (-> (gpt/point frame-corner) tpos-ref (mf/with-memo [page-id]
(gmt/translate-matrix)) (-> (l/in [:pages page-id :options :comment-threads-position])
(l/derived refs/viewer-data)))
cstate (mf/deref refs/comments-local) positions (mf/deref tpos-ref)
threads-map (mf/deref refs/comment-threads)
update-thread-position (fn update-thread-position [thread] frame-corner (mf/with-memo [frame]
(if (contains? threads-position-map (:id thread)) (-> frame :points gsh/points->selrect gpt/point))
(-> thread
(assoc :position (get-in threads-position-map [(:id thread) :position]))
(assoc :frame-id (get-in threads-position-map [(:id thread) :frame-id])))
thread))
threads (->> (vals threads-map) modifier1 (mf/with-memo [frame-corner]
(map update-thread-position) (-> (gmt/matrix)
(gmt/translate (gpt/negate frame-corner))))
modifier2 (mf/with-memo [frame-corner]
(-> (gpt/point frame-corner)
(gmt/translate-matrix)))
threads (mf/with-memo [threads-map positions]
(->> (vals threads-map)
(map (partial update-thread-position positions))
(filter #(= (:frame-id %) (:id frame))) (filter #(= (:frame-id %) (:id frame)))
(dcm/apply-filters cstate profile) (dcm/apply-filters local profile)
(filter (fn [{:keys [position]}] (filter (fn [{:keys [position]}]
(gsh/has-point? frame position)))) (gsh/has-point? frame position)))))
on-bubble-click on-bubble-click
(mf/use-callback (mf/use-fn
(mf/deps cstate) (mf/deps open-thread-id)
(fn [thread] (fn [{:keys [id] :as thread}]
(if (= (:open cstate) (:id thread)) (st/emit! (if (= open-thread-id id)
(st/emit! (dcm/close-thread)) (dcm/close-thread)
(st/emit! (-> (dcm/open-thread thread) (-> (dcm/open-thread thread)
(with-meta {::ev/origin "viewer"})))))) (with-meta {::ev/origin "viewer"}))))))
on-click on-click
(mf/use-callback (mf/use-fn
(mf/deps cstate frame page file zoom) (mf/deps open-thread-id zoom page-id file-id modifier2)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(if (some? (:open cstate)) (if (some? open-thread-id)
(st/emit! (dcm/close-thread)) (st/emit! (dcm/close-thread))
(let [event (.-nativeEvent ^js event) (let [event (dom/event->native-event event)
viewport-point (dom/get-offset-position event) position (-> (dom/get-offset-position event)
viewport-point (-> viewport-point (update :x #(/ % zoom)) (update :y #(/ % zoom))) (update :x #(/ % zoom))
position (gpt/transform viewport-point modifier2) (update :y #(/ % zoom))
(gpt/transform modifier2))
params {:position position params {:position position
:page-id (:id page) :page-id (:id page)
:file-id (:id file)}] :file-id (:id file)}]
(st/emit! (dcm/create-draft params)))))) (st/emit! (dcm/create-draft params))))))
on-draft-cancel on-draft-cancel
(mf/use-callback (mf/use-fn #(st/emit! (dcm/close-thread)))
(mf/deps cstate)
#(st/emit! (dcm/close-thread)))
on-draft-submit on-draft-submit
(mf/use-callback (mf/use-fn
(mf/deps frame) (mf/deps frame-id modifier2)
(fn [draft] (fn [draft]
(let [params (-> draft (let [params (-> draft
(update :position gpt/transform modifier2) (update :position gpt/transform modifier2)
(assoc :frame-id (:id frame)))] (assoc :frame-id frame-id))]
(st/emit! (dcm/create-thread-on-viewer params) (st/emit! (dcm/create-thread-on-viewer params)
(dcm/close-thread)))))] (dcm/close-thread)))))]
@ -156,24 +170,26 @@
[:div.viewer-comments-container [:div.viewer-comments-container
[:div.threads [:div.threads
(for [item threads] (for [item threads]
(let [item (update item :position gpt/transform modifier1)] [:& cmt/thread-bubble
[:& cmt/thread-bubble {:thread item {:thread item
:position-modifier modifier1
:zoom zoom :zoom zoom
:on-click on-bubble-click :on-click on-bubble-click
:open? (= (:id item) (:open cstate)) :open? (= (:id item) (:open local))
:key (:seqn item) :key (:seqn item)
:origin :viewer}])) :origin :viewer}])
(when-let [id (:open cstate)] (when-let [thread (get threads-map open-thread-id)]
(when-let [thread (as-> (get threads-map id) $ [:& cmt/thread-comments
(when (some? $) {:thread thread
(update $ :position gpt/transform modifier1)))] :position-modifier modifier1
[:& cmt/thread-comments {:thread thread
:users users :users users
:zoom zoom}])) :zoom zoom}])
(when-let [draft (:draft cstate)] (when-let [draft (:draft local)]
[:& cmt/draft-thread {:draft (update draft :position gpt/transform modifier1) [:& cmt/draft-thread
{:draft draft
:position-modifier modifier1
:on-cancel on-draft-cancel :on-cancel on-draft-cancel
:on-submit on-draft-submit :on-submit on-draft-submit
:zoom zoom}])]]])) :zoom zoom}])]]]))
@ -181,10 +197,10 @@
(mf/defc comments-sidebar (mf/defc comments-sidebar
[{:keys [users frame page]}] [{:keys [users frame page]}]
(let [profile (mf/deref refs/profile) (let [profile (mf/deref refs/profile)
cstate (mf/deref refs/comments-local) local (mf/deref refs/comments-local)
threads-map (mf/deref threads-ref) threads-map (mf/deref refs/comment-threads)
threads (->> (vals threads-map) threads (->> (vals threads-map)
(dcm/apply-filters cstate profile) (dcm/apply-filters local profile)
(filter (fn [{:keys [position]}] (filter (fn [{:keys [position]}]
(gsh/has-point? frame position))))] (gsh/has-point? frame position))))]
[:aside.settings-bar.settings-bar-right.comments-right-sidebar [:aside.settings-bar.settings-bar-right.comments-right-sidebar

View file

@ -188,8 +188,7 @@
(mf/defc render-frame-svg (mf/defc render-frame-svg
[{:keys [page frame local size]}] [{:keys [page frame local size]}]
(let [objects (mf/use-memo (let [objects (mf/with-memo [page frame size]
(mf/deps page frame)
(prepare-objects page frame size)) (prepare-objects page frame size))
;; Retrieve frame again with correct modifier ;; Retrieve frame again with correct modifier

View file

@ -6,10 +6,10 @@
(ns app.main.ui.viewer.header (ns app.main.ui.viewer.header
(:require (:require
[app.common.data.macros :as dm]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.viewer :as dv] [app.main.data.viewer :as dv]
[app.main.data.viewer.shortcuts :as sc] [app.main.data.viewer.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.export :refer [export-progress-widget]] [app.main.ui.export :refer [export-progress-widget]]
@ -19,8 +19,14 @@
[app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]] [app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[okulary.core :as l]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def fullscreen-ref
(l/derived (fn [state]
(dm/get-in state [:viewer-local :fullscreen?]))
st/state))
(defn open-login-dialog (defn open-login-dialog
[] []
(modal/show! :login-register {})) (modal/show! :login-register {}))
@ -65,7 +71,7 @@
(mf/defc header-options (mf/defc header-options
[{:keys [section zoom page file index permissions]}] [{:keys [section zoom page file index permissions]}]
(let [fullscreen? (mf/deref refs/viewer-fullscreen?) (let [fullscreen? (mf/deref fullscreen-ref)
toggle-fullscreen toggle-fullscreen
(mf/use-callback (mf/use-callback

View file

@ -17,6 +17,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.viewer.shapes :as shapes] [app.main.ui.viewer.shapes :as shapes]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -27,7 +28,6 @@
(defn prepare-objects (defn prepare-objects
[page frame size] [page frame size]
(fn []
(let [objects (:objects page) (let [objects (:objects page)
frame-id (:id frame) frame-id (:id frame)
modifier (-> (gpt/point (:x size) (:y size)) modifier (-> (gpt/point (:x size) (:y size))
@ -38,34 +38,69 @@
(->> (cph/get-children-ids objects frame-id) (->> (cph/get-children-ids objects frame-id)
(into [frame-id]) (into [frame-id])
(reduce update-fn objects))))) (reduce update-fn objects))))
(mf/defc viewport (mf/defc viewport-svg
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]
[{:keys [page interactions-mode frame base-frame frame-offset size]}] ::mf/wrap-props false}
(let [objects (mf/use-memo [props]
(mf/deps page frame size) (let [page (unchecked-get props "page")
frame (unchecked-get props "frame")
base (unchecked-get props "base")
offset (unchecked-get props "offset")
size (unchecked-get props "size")
vbox (:vbox size)
objects (mf/with-memo [page frame size]
(prepare-objects page frame size)) (prepare-objects page frame size))
wrapper (mf/use-memo wrapper (mf/with-memo [objects]
(mf/deps objects) (shapes/frame-container-factory objects))
#(shapes/frame-container-factory objects))
;; Retrieve frames again with correct modifier ;; Retrieve frames again with correct modifier
frame (get objects (:id frame)) frame (get objects (:id frame))
base-frame (get objects (:id base-frame)) base (get objects (:id base))]
on-click [:& (mf/provider shapes/base-frame-ctx) {:value base}
[:& (mf/provider shapes/frame-offset-ctx) {:value offset}
[:svg {:view-box vbox
:width (:width size)
:height (:height size)
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"
:fill "none"}
[:& wrapper {:shape frame :view-box vbox}]]]]))
(mf/defc viewport
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[props]
(let [;; NOTE: with `use-equal-memo` hook we ensure that all values
;; conserves the reference identity for avoid unnecesary dummy
;; rerenders.
mode (h/use-equal-memo (unchecked-get props "interactions-mode"))
offset (h/use-equal-memo (unchecked-get props "frame-offset"))
size (h/use-equal-memo (unchecked-get props "size"))
page (unchecked-get props "page")
frame (unchecked-get props "frame")
base (unchecked-get props "base-frame")]
(mf/with-effect [mode]
(let [on-click
(fn [_] (fn [_]
(when (= interactions-mode :show-on-click) (when (= mode :show-on-click)
(st/emit! dv/flash-interactions))) (st/emit! (dv/flash-interactions))))
on-mouse-wheel on-mouse-wheel
(fn [event] (fn [event]
(when (kbd/mod? event) (when (kbd/mod? event)
(dom/prevent-default event) (dom/prevent-default event)
(let [event (.getBrowserEvent ^js event) (let [event (dom/event->browser-event event)
delta (+ (.-deltaY ^js event) (.-deltaX ^js event))] delta (+ (.-deltaY ^js event)
(.-deltaX ^js event))]
(if (pos? delta) (if (pos? delta)
(st/emit! dv/decrease-zoom) (st/emit! dv/decrease-zoom)
(st/emit! dv/increase-zoom))))) (st/emit! dv/increase-zoom)))))
@ -73,38 +108,29 @@
on-key-down on-key-down
(fn [event] (fn [event]
(when (kbd/esc? event) (when (kbd/esc? event)
(st/emit! (dcm/close-thread))))] (st/emit! (dcm/close-thread))))
(mf/use-effect
(mf/deps interactions-mode) ;; on-click event depends on interactions-mode
(fn []
;; bind with passive=false to allow the event to be cancelled ;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895 ;; https://stackoverflow.com/a/57582286/3219895
(let [key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false}) key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false})
key2 (events/listen js/window "keydown" on-key-down) key2 (events/listen goog/global "keydown" on-key-down)
key3 (events/listen js/window "click" on-click)] key3 (events/listen goog/global "click" on-click)]
(fn [] (fn []
(events/unlistenByKey key1) (events/unlistenByKey key1)
(events/unlistenByKey key2) (events/unlistenByKey key2)
(events/unlistenByKey key3))))) (events/unlistenByKey key3))))
[:& (mf/provider shapes/base-frame-ctx) {:value base-frame}
[:& (mf/provider shapes/frame-offset-ctx) {:value frame-offset}
[:svg {:view-box (:vbox size)
:width (:width size)
:height (:height size)
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"
:fill "none"}
[:& wrapper {:shape frame
:view-box (:vbox size)}]]]]))
[:& viewport-svg {:page page
:frame frame
:base base
:offset offset
:size size}]))
(mf/defc flows-menu (mf/defc flows-menu
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [page index]}] [{:keys [page index]}]
(let [flows (get-in page [:options :flows]) (let [flows (dm/get-in page [:options :flows])
frames (:frames page) frames (:frames page)
frame (get frames index) frame (get frames index)
current-flow (mf/use-state current-flow (mf/use-state
@ -135,7 +161,6 @@
[:span.icon i/tick] [:span.icon i/tick]
[:span.label (:name flow)]])]]]))) [:span.label (:name flow)]])]]])))
(mf/defc interactions-menu (mf/defc interactions-menu
[] []
(let [local (mf/deref refs/viewer-local) (let [local (mf/deref refs/viewer-local)

View file

@ -206,6 +206,9 @@
:style {:pointer-events (when frame? "none")} :style {:pointer-events (when frame? "none")}
:transform (gsh/transform-str shape)}]))) :transform (gsh/transform-str shape)}])))
;; TODO: use-memo use-fn
(defn generic-wrapper-factory (defn generic-wrapper-factory
"Wrap some svg shape and add interaction controls" "Wrap some svg shape and add interaction controls"
[component] [component]
@ -226,20 +229,37 @@
interactions (:interactions shape) interactions (:interactions shape)
svg-element? (and (= :svg-raw (:type shape)) svg-element? (and (= :svg-raw (:type shape))
(not= :svg (get-in shape [:content :tag])))] (not= :svg (get-in shape [:content :tag])))
(mf/use-effect
(fn [] on-mouse-down
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
#(on-mouse-down % shape base-frame frame-offset objects))
on-mouse-up
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
#(on-mouse-up % shape base-frame frame-offset objects))
on-mouse-enter
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
#(on-mouse-enter % shape base-frame frame-offset objects))
on-mouse-leave
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
#(on-mouse-leave % shape base-frame frame-offset objects))]
(mf/with-effect []
(let [sems (on-load shape base-frame frame-offset objects)] (let [sems (on-load shape base-frame frame-offset objects)]
#(run! tm/dispose! sems)))) (partial run! tm/dispose! sems)))
(if-not svg-element? (if-not svg-element?
[:> shape-container {:shape shape [:> shape-container {:shape shape
:cursor (when (ctsi/actionable? interactions) "pointer") :cursor (when (ctsi/actionable? interactions) "pointer")
:on-mouse-down #(on-mouse-down % shape base-frame frame-offset objects) :on-mouse-down on-mouse-down
:on-mouse-up #(on-mouse-up % shape base-frame frame-offset objects) :on-mouse-up on-mouse-up
:on-mouse-enter #(on-mouse-enter % shape base-frame frame-offset objects) :on-mouse-enter on-mouse-enter
:on-mouse-leave #(on-mouse-leave % shape base-frame frame-offset objects)} :on-mouse-leave on-mouse-leave}
[:& component {:shape shape [:& component {:shape shape
:frame frame :frame frame
@ -311,6 +331,7 @@
#js {:shape shape #js {:shape shape
:childs childs :childs childs
:objects objects})] :objects objects})]
[:> frame-wrapper props])))) [:> frame-wrapper props]))))
(defn group-container-factory (defn group-container-factory
@ -362,31 +383,43 @@
image-wrapper (image-wrapper) image-wrapper (image-wrapper)
circle-wrapper (circle-wrapper)] circle-wrapper (circle-wrapper)]
(mf/fnc shape-container (mf/fnc shape-container
{::mf/wrap-props false} {::mf/wrap-props false
::mf/wrap [mf/memo]}
[props] [props]
(let [scroll (mf/use-ctx ctx/scroll-ctx) (let [shape (unchecked-get props "shape")
local (mf/deref refs/viewer-local)
zoom (:zoom local)
shape (unchecked-get props "shape")
parents (map (d/getf objects) (cph/get-parent-ids objects (:id shape)))
fixed? (or (:fixed-scroll shape) (some :fixed-scroll parents))
frame (unchecked-get props "frame") frame (unchecked-get props "frame")
delta {:x (/ (:scroll-left scroll) zoom) :y (/ (:scroll-top scroll) zoom)}
;; TODO: this watch of scroll position is killing
;; performance of the viewer.
scroll (mf/use-ctx ctx/current-scroll)
zoom (mf/use-ctx ctx/current-zoom)
fixed? (mf/with-memo [shape objects]
(->> (cph/get-parent-ids objects (:id shape))
(map (d/getf objects))
(concat [shape])
(some :fixed-scroll)))
delta {:x (/ (:scroll-left scroll) zoom)
:y (/ (:scroll-top scroll) zoom)}
group-container group-container
(mf/use-memo (mf/deps objects) (mf/with-memo [objects]
#(group-container-factory objects)) (group-container-factory objects))
frame-container frame-container
(mf/use-memo (mf/deps objects) (mf/with-memo [objects]
#(frame-container-factory objects)) (frame-container-factory objects))
bool-container bool-container
(mf/use-memo (mf/deps objects) (mf/with-memo [objects]
#(bool-container-factory objects)) (bool-container-factory objects))
svg-raw-container svg-raw-container
(mf/use-memo (mf/deps objects) (mf/with-memo [objects]
#(svg-raw-container-factory objects))] (svg-raw-container-factory objects))
]
(when (and shape (not (:hidden shape))) (when (and shape (not (:hidden shape)))
(let [shape (-> (gsh/transform-shape shape) (let [shape (-> (gsh/transform-shape shape)
(gsh/translate-to-frame frame) (gsh/translate-to-frame frame)
@ -404,4 +437,3 @@
:group [:> group-container {:shape shape :frame frame :objects objects :fixed? fixed? :delta delta}] :group [:> group-container {:shape shape :frame frame :objects objects :fixed? fixed? :delta delta}]
:bool [:> bool-container {:shape shape :frame frame :objects objects}] :bool [:> bool-container {:shape shape :frame frame :objects objects}]
:svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}]))))))) :svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}])))))))

View file

@ -8,6 +8,7 @@
"A workspace specific context menu (mouse right click)." "A workspace specific context menu (mouse right click)."
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.main.data.events :as ev] [app.main.data.events :as ev]
@ -123,17 +124,25 @@
[:& menu-separator]])) [:& menu-separator]]))
(mf/defc context-menu-layer-position (mf/defc context-menu-layer-position
[{:keys [hover-objs shapes]}] [{:keys [shapes]}]
(let [do-bring-forward #(st/emit! (dw/vertical-order-selected :up)) (let [do-bring-forward (mf/use-fn #(st/emit! (dw/vertical-order-selected :up)))
do-bring-to-front #(st/emit! (dw/vertical-order-selected :top)) do-bring-to-front (mf/use-fn #(st/emit! (dw/vertical-order-selected :top)))
do-send-backward #(st/emit! (dw/vertical-order-selected :down)) do-send-backward (mf/use-fn #(st/emit! (dw/vertical-order-selected :down)))
do-send-to-back #(st/emit! (dw/vertical-order-selected :bottom)) do-send-to-back (mf/use-fn #(st/emit! (dw/vertical-order-selected :bottom)))
select-shapes (fn [id] #(st/emit! (dws/select-shape id)))] select-shapes (fn [id] #(st/emit! (dws/select-shape id)))
;; NOTE: we use deref instead of mf/deref on objects because
;; we really don't want rerender on object changes
hover-ids (deref refs/current-hover-ids)
objects (deref refs/workspace-page-objects)
hover-objs (into [] (keep (d/getf objects)) hover-ids)]
[:* [:*
(when (> (count hover-objs) 1) (when (> (count hover-objs) 1)
[:& menu-entry {:title (tr "workspace.shape.menu.select-layer")} [:& menu-entry {:title (tr "workspace.shape.menu.select-layer")}
(for [object hover-objs] (for [object hover-objs]
[:& menu-entry {:title (:name object) [:& menu-entry {:title (:name object)
:key (dm/str (:id object))
:selected? (some #(= object %) shapes) :selected? (some #(= object %) shapes)
:on-click (select-shapes (:id object)) :on-click (select-shapes (:id object))
:icon (si/element-icon {:shape object})}])]) :icon (si/element-icon {:shape object})}])])
@ -435,14 +444,11 @@
:on-click do-delete}])) :on-click do-delete}]))
(mf/defc shape-context-menu (mf/defc shape-context-menu
{::mf/wrap [mf/memo]}
[{:keys [mdata] :as props}] [{:keys [mdata] :as props}]
(let [{:keys [disable-booleans? disable-flatten?]} mdata (let [{:keys [disable-booleans? disable-flatten?]} mdata
shapes (mf/deref refs/selected-objects) shapes (mf/deref refs/selected-objects)
hover-ids (mf/deref refs/current-hover-ids)
hover-objs (mf/deref (refs/objects-by-id hover-ids))
props #js {:shapes shapes props #js {:shapes shapes
:hover-objs hover-objs
:disable-booleans? disable-booleans? :disable-booleans? disable-booleans?
:disable-flatten? disable-flatten?}] :disable-flatten? disable-flatten?}]
(when-not (empty? shapes) (when-not (empty? shapes)

View file

@ -54,7 +54,7 @@
(mf/deps objects) (mf/deps objects)
#(cph/objects-by-frame objects))] #(cph/objects-by-frame objects))]
[:& (mf/provider ctx/active-frames-ctx) {:value active-frames} [:& (mf/provider ctx/active-frames) {:value active-frames}
;; Render font faces only for shapes that are part of the root ;; Render font faces only for shapes that are part of the root
;; frame but don't belongs to any other frame. ;; frame but don't belongs to any other frame.
(let [xform (comp (let [xform (comp
@ -79,7 +79,7 @@
(let [shape (obj/get props "shape") (let [shape (obj/get props "shape")
active-frames active-frames
(when (cph/root-frame? shape) (mf/use-ctx ctx/active-frames-ctx)) (when (cph/root-frame? shape) (mf/use-ctx ctx/active-frames))
thumbnail? thumbnail?
(and (some? active-frames) (and (some? active-frames)

View file

@ -37,6 +37,14 @@
(when (some? e) (when (some? e)
(.-target e))) (.-target e)))
(defn event->native-event
[^js e]
(.-nativeEvent e))
(defn event->browser-event
[^js e]
(.getBrowserEvent e))
;; --- New methods ;; --- New methods
(declare get-elements-by-tag) (declare get-elements-by-tag)