diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index e09d8eff8..50f6dcff0 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -342,6 +342,13 @@ (some? 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/keys :req-un [::page-id ::file-id ::position])) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index d920b7685..c8061dac6 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -7,6 +7,7 @@ (ns app.main.data.viewer (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -20,6 +21,9 @@ [cljs.spec.alpha :as s] [potok.core :as ptk])) +(s/def ::nilable-boolean (s/nilable ::us/boolean)) +(s/def ::nilable-animation (s/nilable ::ctsi/animation)) + ;; --- Local State Initialization (def ^:private @@ -32,7 +36,6 @@ :comments-show :unresolved :selected #{} :collapsed #{} - :overlays [] :hover nil :share-id "" :file-comments-users []}) @@ -329,7 +332,8 @@ (declare flash-done) -(def flash-interactions +(defn flash-interactions + [] (ptk/reify ::flash-interactions ptk/UpdateEvent (update [_ state] @@ -367,7 +371,7 @@ (ptk/reify ::complete-animation ptk/UpdateEvent (update [_ state] - (d/dissoc-in state [:viewer-local :current-animation])))) + (dissoc state :viewer-animation)))) ;; --- Navigation inside page @@ -376,7 +380,7 @@ (ptk/reify ::go-to-frame-by-index ptk/UpdateEvent (update [_ state] - (assoc-in state [:viewer-local :overlays] [])) + (assoc state :viewer-overlays [])) ptk/WatchEvent (watch [_ state _] @@ -391,8 +395,9 @@ (go-to-frame frame-id nil)) ([frame-id animation] - (us/verify ::us/uuid frame-id) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::nilable-animation animation) + (ptk/reify ::go-to-frame ptk/UpdateEvent (update [_ state] @@ -404,13 +409,13 @@ frame (get frames index)] (cond-> state :always - (assoc-in [:viewer-local :overlays] []) + (assoc :viewer-overlays []) (some? animation) - (assoc-in [:viewer-local :current-animation] - {:kind :go-to-frame - :orig-frame-id (:id frame) - :animation animation})))) + (assoc :viewer-animation + {:kind :go-to-frame + :orig-frame-id (:id frame) + :animation animation})))) ptk/WatchEvent (watch [_ state _] @@ -440,7 +445,7 @@ (ptk/reify ::go-to-section ptk/UpdateEvent (update [_ state] - (assoc-in state [:viewer-local :overlays] [])) + (assoc state :viewer-overlays [])) ptk/WatchEvent (watch [_ state _] @@ -451,64 +456,69 @@ ;; --- Overlays -(defn- do-open-overlay +(defn- open-overlay* [state frame position close-click-outside background-overlay animation] (cond-> state :always - (update-in [:viewer-local :overlays] conj - {:frame frame - :position position - :close-click-outside close-click-outside - :background-overlay background-overlay}) - (some? animation) - (assoc-in [:viewer-local :current-animation] - {:kind :open-overlay - :overlay-id (:id frame) - :animation animation}))) + (update :viewer-overlays conj + {:frame frame + :id (:id frame) + :position position + :close-click-outside close-click-outside + :background-overlay background-overlay}) -(defn- do-close-overlay + (some? animation) + (assoc :viewer-animation + {:kind :open-overlay + :overlay-id (:id frame) + :animation animation}))) + +(defn- close-overlay* [state frame-id animation] (if (nil? animation) - (update-in state [:viewer-local :overlays] - (fn [overlays] - (d/removev #(= (:id (:frame %)) frame-id) overlays))) - (assoc-in state [:viewer-local :current-animation] - {:kind :close-overlay - :overlay-id frame-id - :animation animation}))) + (update state :viewer-overlays + (fn [overlays] + (d/removev #(= (:id (:frame %)) frame-id) overlays))) + (assoc state :viewer-animation + {:kind :close-overlay + :overlay-id frame-id + :animation animation}))) (defn open-overlay [frame-id position close-click-outside background-overlay animation] - (us/verify ::us/uuid frame-id) - (us/verify ::gpt/point position) - (us/verify (s/nilable ::us/boolean) close-click-outside) - (us/verify (s/nilable ::us/boolean) background-overlay) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::gpt/point position) + (us/assert! ::nilable-boolean close-click-outside) + (us/assert! ::nilable-boolean background-overlay) + (us/assert! ::nilable-animation animation) + (ptk/reify ::open-overlay ptk/UpdateEvent (update [_ state] (let [route (:route state) qparams (:query-params route) 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) - overlays (get-in state [:viewer-local :overlays])] + overlays (:viewer-overlays state)] (if-not (some #(= (:frame %) frame) overlays) - (do-open-overlay state - frame - position - close-click-outside - background-overlay - animation) + (open-overlay* state + frame + position + close-click-outside + background-overlay + animation) state))))) + (defn toggle-overlay [frame-id position close-click-outside background-overlay animation] - (us/verify ::us/uuid frame-id) - (us/verify ::gpt/point position) - (us/verify (s/nilable ::us/boolean) close-click-outside) - (us/verify (s/nilable ::us/boolean) background-overlay) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::gpt/point position) + (us/assert! ::nilable-boolean close-click-outside) + (us/assert! ::nilable-boolean background-overlay) + (us/assert! ::nilable-animation animation) + (ptk/reify ::toggle-overlay ptk/UpdateEvent (update [_ state] @@ -517,29 +527,30 @@ page-id (:page-id qparams) frames (get-in state [:viewer :pages page-id :all-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) - (do-open-overlay state - frame - position - close-click-outside - background-overlay - animation) - (do-close-overlay state - (:id frame) - (ctsi/invert-direction animation))))))) + (open-overlay* state + frame + position + close-click-outside + background-overlay + animation) + (close-overlay* state + (:id frame) + (ctsi/invert-direction animation))))))) (defn close-overlay ([frame-id] (close-overlay frame-id nil)) ([frame-id animation] - (us/verify ::us/uuid frame-id) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::nilable-animation animation) + (ptk/reify ::close-overlay ptk/UpdateEvent (update [_ state] - (do-close-overlay state - frame-id - animation))))) + (close-overlay* state + frame-id + animation))))) ;; --- Objects selection diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 34a3518c9..315324668 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -21,6 +21,8 @@ (def current-project-id (mf/create-context nil)) (def current-page-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 current-zoom (mf/create-context nil)) + (def active-frames-ctx (mf/create-context nil)) -(def render-thumbnails (mf/create-context nil)) +(def render-thumbnails (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index c7ea0ebf3..3b868ca9d 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -11,6 +11,7 @@ [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.main.ui.context :as muc] + [app.main.ui.hooks :as h] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.fills :as fills] @@ -49,18 +50,19 @@ {::mf/forward-ref true ::mf/wrap-props false} [props ref] - (let [shape (obj/get props "shape") - children (obj/get props "children") - pointer-events (obj/get props "pointer-events") - disable-shadows? (obj/get props "disable-shadows?") - type (:type shape) - render-id (mf/use-memo #(str (uuid/next))) - filter-id (str "filter_" render-id) - styles (-> (obj/create) - (obj/set! "pointerEvents" pointer-events) - (cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal)) - (obj/set! "mixBlendMode" (d/name (:blend-mode shape))))) + (let [shape (unchecked-get props "shape") + children (unchecked-get props "children") + pointer-events (unchecked-get props "pointer-events") + disable-shadows? (unchecked-get props "disable-shadows?") + + type (:type shape) + render-id (h/use-id) + filter-id (dm/str "filter_" render-id) + styles (-> (obj/create) + (obj/set! "pointerEvents" pointer-events) + (cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal)) + (obj/set! "mixBlendMode" (d/name (:blend-mode shape))))) include-metadata? (mf/use-ctx ed/include-metadata-ctx) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index f2f86ace9..a066c1388 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -27,7 +27,7 @@ [app.main.ui.static :as static] [app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]] [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.login] [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]] @@ -36,8 +36,15 @@ [app.util.webapi :as wapi] [cuerdas.core :as str] [goog.events :as events] + [okulary.core :as l] [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 [objects frame zoom] (let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)] @@ -60,7 +67,6 @@ :height (* height zoom) :vbox (str "0 0 " width " " height)}))) - (mf/defc viewer-pagination [{:keys [index num-frames left-bar right-bar] :as props}] [:* @@ -75,94 +81,134 @@ [:div.counter (str/join " / " [(+ index 1) num-frames])] [:span]]]) +(mf/defc viewer-pagination-and-sidebar + {::mf/wrap [mf/memo]} + [{:keys [section index frames users frame page]}] + (let [comments-local (mf/deref refs/comments-local) + show-sidebar? (and (= section :comments) (:show-sidebar? comments-local))] + [:* + [:& viewer-pagination + {:index index + :num-frames (count (:frames page)) + :right-bar show-sidebar?}] + + (when show-sidebar? + [:& comments-sidebar + {:users users + :frame frame + :page page}])])) + +(mf/defc viewer-overlay + [{:keys [overlay file 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 scroll 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}] - (let [{clist :list} (mf/deref refs/comments-local) - show-comments-list (and (= section :comments) (= :show clist))] - [:* - [:& viewer-pagination {:index index :num-frames (count (:frames page)) :right-bar show-comments-list}] + [:* + [:& viewer-pagination-and-sidebar + {:section section + :index index + :page page + :users users + :frame frame}] - (when show-comments-list - [:& comments-sidebar {:users users :frame frame :page page}]) + [:div.viewer-wrapper + {:style {:width (:width wrapper-size) + :height (:height wrapper-size)}} + [:div.viewer-clipper + (when orig-frame + [:div.viewport-container + {:ref orig-viewport-ref + :style {:width (:width orig-size) + :height (:height orig-size) + :position "relative"}} - [:div.viewer-wrapper - {:style {:width (:width wrapper-size) - :height (:height wrapper-size)}} - [:& (mf/provider ctx/scroll-ctx) {:value @scroll} - [:div.viewer-clipper - [:* - (when orig-frame - [:div.viewport-container - {:ref orig-viewport-ref - :style {:width (:width orig-size) - :height (:height orig-size) - :position "relative"}} + [:& interactions/viewport + {:frame orig-frame + :base-frame orig-frame + :frame-offset (gpt/point 0 0) + :size orig-size + :page page + :file file + :users users + :interactions-mode :hide}]]) - [:& interactions/viewport - {:frame orig-frame - :base-frame orig-frame - :frame-offset (gpt/point 0 0) - :size orig-size - :page page - :file file - :users users - :interactions-mode :hide}]]) + [:div.viewport-container + {:ref current-viewport-ref + :style {:width (:width size) + :height (:height size) + :position "relative"}} - [:div.viewport-container - {:ref current-viewport-ref - :style {:width (:width size) - :height (:height size) - :position "relative"}} + [:& interactions/viewport + {:frame frame + :base-frame frame + :frame-offset (gpt/point 0 0) + :size size + :page page + :interactions-mode interactions-mode}] - [:& interactions/viewport - {:frame frame - :base-frame frame - :frame-offset (gpt/point 0 0) - :size size - :page page - :file file - :users users - :interactions-mode interactions-mode}] + (for [overlay overlays] + [:& viewer-overlay {:overlay overlay + :file file + :key (dm/str (:id overlay)) + :page page + :frame frame + :zoom zoom + :wrapper-size wrapper-size + :close-overlay close-overlay + :interactions-mode interactions-mode}]) - (for [overlay overlays] - (let [size-over (calculate-size (:objects page) (:frame overlay) zoom)] - [:* - (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 - :file file - :users users - :interactions-mode interactions-mode}]]]))]] - (when (= section :comments) - [:& comments-layer {:file file - :users users - :frame frame - :page page - :zoom zoom}])]]]])) + (when (= section :comments) + [:& comments-layer {:file file + :users users + :frame frame + :page page + :zoom zoom}])]]) (mf/defc viewer [{:keys [params data]}] @@ -184,10 +230,11 @@ local (mf/deref refs/viewer-local) nav-scroll (:nav-scroll local) - orig-viewport-ref (mf/use-ref nil) + orig-viewport-ref (mf/use-ref nil) current-viewport-ref (mf/use-ref nil) - viewer-section-ref (mf/use-ref nil) - current-animation (:current-animation local) + viewer-section-ref (mf/use-ref nil) + + current-animation (mf/deref current-animation-ref) page-id (or page-id (-> file :data :pages first)) @@ -206,47 +253,49 @@ frames (:frames page) frame (get frames index) - fullscreen? (mf/deref refs/viewer-fullscreen?) - overlays (:overlays local) - scroll (mf/use-state nil) + fullscreen? (mf/deref header/fullscreen-ref) + overlays (:overlays local) + overlays (mf/deref current-overlays-ref) + scroll (mf/use-state nil) orig-frame (when (:orig-frame-id current-animation) (d/seek #(= (:id %) (:orig-frame-id current-animation)) frames)) - size (mf/use-memo - (mf/deps frame zoom) - (fn [] (calculate-size (:objects page) frame zoom))) + size + (mf/with-memo [frame zoom] + (calculate-size (:objects page) frame zoom)) - orig-size (mf/use-memo - (mf/deps orig-frame zoom) - (fn [] (when orig-frame - (calculate-size (:objects page) orig-frame zoom)))) + orig-size + (mf/with-memo [orig-frame zoom] + (when orig-frame + (calculate-size (:objects page) orig-frame zoom))) - wrapper-size (mf/use-memo - (mf/deps size orig-size zoom) - (fn [] (calculate-wrapper size orig-size zoom))) + wrapper-size + (mf/with-memo [size orig-size zoom] + (calculate-wrapper size orig-size zoom)) interactions-mode (:interactions-mode local) on-click - (mf/use-callback + (mf/use-fn (mf/deps section) (fn [_] (when (= section :comments) (st/emit! (dcm/close-thread))))) set-up-new-size - (mf/use-callback + (mf/use-fn (fn [_] (let [viewer-section (dom/get-element "viewer-section") size (dom/get-client-size viewer-section)] (st/emit! (dv/set-viewport-size {:size size}))))) on-scroll - (fn [event] - (reset! scroll (dom/get-target-scroll event)))] + (mf/use-fn + (fn [event] + (reset! scroll (dom/get-target-scroll event))))] (hooks/use-shortcuts ::viewer sc/shortcuts) @@ -264,14 +313,13 @@ (let [name (:name file)] (dom/set-html-title (str "\u25b6 " (tr "title.viewer" name)))))) - (mf/use-effect - (fn [] - (dom/set-html-theme-color clr/gray-50 "dark") - (let [key1 (events/listen js/window "click" on-click) - key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)] - (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2))))) + (mf/with-effect [] + (dom/set-html-theme-color clr/gray-50 "dark") + (let [key1 (events/listen js/window "click" on-click) + key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)] + (fn [] + (events/unlistenByKey key1) + (events/unlistenByKey key2)))) (mf/use-layout-effect (fn [] @@ -362,20 +410,22 @@ fonts (into #{} (keep :font-id) text-nodes)] (run! fonts/ensure-loaded! fonts)))) - [:div#viewer-layout {:class (dom/classnames - :force-visible (:show-thumbnails local) - :viewer-layout (not= section :handoff) - :handoff-layout (= section :handoff) - :fullscreen fullscreen?)} + [:div#viewer-layout + {:class (dom/classnames + :force-visible (:show-thumbnails local) + :viewer-layout (not= section :handoff) + :handoff-layout (= section :handoff) + :fullscreen fullscreen?)} - [:& header {:project project - :index index - :file file - :page page - :frame frame - :permissions permissions - :zoom zoom - :section section}] + [:& header/header + {:project project + :index index + :file file + :page page + :frame frame + :permissions permissions + :zoom zoom + :section section}] [:div.viewer-content [:div.thumbnail-close {:on-click #(st/emit! dv/close-thumbnails-panel) @@ -410,23 +460,25 @@ :index index :viewer-pagination viewer-pagination}] - [:& viewer-wrapper - {:wrapper-size wrapper-size - :scroll scroll - :orig-frame orig-frame - :orig-viewport-ref orig-viewport-ref - :orig-size orig-size - :page page - :file file - :users users - :current-viewport-ref current-viewport-ref - :size size - :frame frame - :interactions-mode interactions-mode - :overlays overlays - :zoom zoom - :section section - :index index}]))]]])) + [:& (mf/provider ctx/current-scroll) {:value @scroll} + [:& (mf/provider ctx/current-zoom) {:value zoom} + [:& viewer-wrapper + {:wrapper-size wrapper-size + :scroll @scroll + :orig-frame orig-frame + :orig-viewport-ref orig-viewport-ref + :orig-size orig-size + :page page + :file file + :users users + :current-viewport-ref current-viewport-ref + :size size + :frame frame + :interactions-mode interactions-mode + :overlays overlays + :zoom zoom + :section section + :index index}]]]))]]])) ;; --- Component: Viewer Page diff --git a/frontend/src/app/main/ui/viewer/handoff.cljs b/frontend/src/app/main/ui/viewer/handoff.cljs index 8a238fea6..2eb108de6 100644 --- a/frontend/src/app/main/ui/viewer/handoff.cljs +++ b/frontend/src/app/main/ui/viewer/handoff.cljs @@ -6,13 +6,13 @@ (ns app.main.ui.viewer.handoff (:require - [app.main.data.viewer :as dv] + [app.main.data.viewer :as dv] [app.main.store :as st] [app.main.ui.viewer.handoff.left-sidebar :refer [left-sidebar]] [app.main.ui.viewer.handoff.render :refer [render-frame-svg]] [app.main.ui.viewer.handoff.right-sidebar :refer [right-sidebar]] [app.util.dom :as dom] - [app.util.keyboard :as kbd] + [app.util.keyboard :as kbd] [goog.events :as events] [rumext.alpha :as mf]) (:import goog.events.EventType)) diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs index 3cfb179bf..61d982910 100644 --- a/frontend/src/app/main/ui/viewer/handoff/render.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs @@ -188,9 +188,8 @@ (mf/defc render-frame-svg [{:keys [page frame local size]}] - (let [objects (mf/use-memo - (mf/deps page frame) - (prepare-objects page frame size)) + (let [objects (mf/with-memo [page frame size] + (prepare-objects page frame size)) ;; Retrieve frame again with correct modifier frame (get objects (:id frame)) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 034473649..64f835ee7 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.viewer.header (:require + [app.common.data.macros :as dm] [app.main.data.modal :as modal] [app.main.data.viewer :as dv] [app.main.data.viewer.shortcuts :as sc] @@ -19,8 +20,14 @@ [app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] + [okulary.core :as l] [rumext.alpha :as mf])) +(def fullscreen-ref + (l/derived (fn [state] + (dm/get-in state [:viewer-local :fullscreen?])) + st/state)) + (defn open-login-dialog [] (modal/show! :login-register {})) @@ -65,7 +72,7 @@ (mf/defc header-options [{:keys [section zoom page file index permissions]}] - (let [fullscreen? (mf/deref refs/viewer-fullscreen?) + (let [fullscreen? (mf/deref fullscreen-ref) toggle-fullscreen (mf/use-callback diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index 32c2a0c0d..3541f1b70 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -18,93 +18,122 @@ [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] + [app.main.ui.hooks :as h] [app.main.ui.viewer.shapes :as shapes] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] + [app.util.object :as obj] [goog.events :as events] [rumext.alpha :as mf])) (defn prepare-objects [page frame size] - (fn [] - (let [objects (:objects page) - frame-id (:id frame) - modifier (-> (gpt/point (:x size) (:y size)) - (gpt/negate) - (gmt/translate-matrix)) + (let [objects (:objects page) + frame-id (:id frame) + modifier (-> (gpt/point (:x size) (:y size)) + (gpt/negate) + (gmt/translate-matrix)) - update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)] + update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)] - (->> (cph/get-children-ids objects frame-id) - (into [frame-id]) - (reduce update-fn objects))))) + (->> (cph/get-children-ids objects frame-id) + (into [frame-id]) + (reduce update-fn objects)))) -(mf/defc viewport - {::mf/wrap [mf/memo]} - [{:keys [page interactions-mode frame base-frame frame-offset size]}] - (let [objects (mf/use-memo - (mf/deps page frame size) - (prepare-objects page frame size)) +(mf/defc viewport-svg + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [props] + (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") - wrapper (mf/use-memo - (mf/deps objects) - #(shapes/frame-container-factory objects)) + vbox (:vbox size) + + objects (mf/with-memo [page frame size] + (prepare-objects page frame size)) + + wrapper (mf/with-memo [objects] + (shapes/frame-container-factory objects)) ;; Retrieve frames again with correct modifier - frame (get objects (:id frame)) - base-frame (get objects (:id base-frame)) + frame (get objects (:id frame)) + base (get objects (:id base))] - on-click - (fn [_] - (when (= interactions-mode :show-on-click) - (st/emit! dv/flash-interactions))) - - on-mouse-wheel - (fn [event] - (when (kbd/mod? event) - (dom/prevent-default event) - (let [event (.getBrowserEvent ^js event) - delta (+ (.-deltaY ^js event) (.-deltaX ^js event))] - (if (pos? delta) - (st/emit! dv/decrease-zoom) - (st/emit! dv/increase-zoom))))) - - on-key-down - (fn [event] - (when (kbd/esc? event) - (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 - ;; https://stackoverflow.com/a/57582286/3219895 - (let [key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false}) - key2 (events/listen js/window "keydown" on-key-down) - key3 (events/listen js/window "click" on-click)] - (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2) - (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) + [:& (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 size)}]]]])) + [:& 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 [_] + (when (= mode :show-on-click) + (st/emit! (dv/flash-interactions)))) + + on-mouse-wheel + (fn [event] + (when (kbd/mod? event) + (dom/prevent-default event) + (let [event (dom/event->browser-event event) + delta (+ (.-deltaY ^js event) + (.-deltaX ^js event))] + (if (pos? delta) + (st/emit! dv/decrease-zoom) + (st/emit! dv/increase-zoom))))) + + on-key-down + (fn [event] + (when (kbd/esc? event) + (st/emit! (dcm/close-thread)))) + + + ;; bind with passive=false to allow the event to be cancelled + ;; https://stackoverflow.com/a/57582286/3219895 + key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false}) + key2 (events/listen goog/global "keydown" on-key-down) + key3 (events/listen goog/global "click" on-click)] + (fn [] + (events/unlistenByKey key1) + (events/unlistenByKey key2) + (events/unlistenByKey key3)))) + + [:& viewport-svg {:page page + :frame frame + :base base + :offset offset + :size size}])) (mf/defc flows-menu {::mf/wrap [mf/memo]} [{:keys [page index]}] - (let [flows (get-in page [:options :flows]) + (let [flows (dm/get-in page [:options :flows]) frames (:frames page) frame (get frames index) current-flow (mf/use-state @@ -135,7 +164,6 @@ [:span.icon i/tick] [:span.label (:name flow)]])]]]))) - (mf/defc interactions-menu [] (let [local (mf/deref refs/viewer-local) diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 2ffe4f186..563041af7 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -206,19 +206,22 @@ :style {:pointer-events (when frame? "none")} :transform (gsh/transform-str shape)}]))) + +;; TODO: use-memo use-fn + (defn generic-wrapper-factory "Wrap some svg shape and add interaction controls" [component] (mf/fnc generic-wrapper {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - childs (unchecked-get props "childs") - frame (unchecked-get props "frame") - objects (unchecked-get props "objects") - fixed? (unchecked-get props "fixed?") - delta (unchecked-get props "delta") - base-frame (mf/use-ctx base-frame-ctx) + (let [shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + frame (unchecked-get props "frame") + objects (unchecked-get props "objects") + fixed? (unchecked-get props "fixed?") + delta (unchecked-get props "delta") + base-frame (mf/use-ctx base-frame-ctx) frame-offset (mf/use-ctx frame-offset-ctx) interactions-show? (mf/deref viewer-interactions-show?) @@ -226,20 +229,37 @@ interactions (:interactions 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 [] - (let [sems (on-load shape base-frame frame-offset objects)] - #(run! tm/dispose! sems)))) + + 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)] + (partial run! tm/dispose! sems))) (if-not svg-element? [:> shape-container {:shape shape :cursor (when (ctsi/actionable? interactions) "pointer") - :on-mouse-down #(on-mouse-down % shape base-frame frame-offset objects) - :on-mouse-up #(on-mouse-up % shape base-frame frame-offset objects) - :on-mouse-enter #(on-mouse-enter % shape base-frame frame-offset objects) - :on-mouse-leave #(on-mouse-leave % shape base-frame frame-offset objects)} + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up + :on-mouse-enter on-mouse-enter + :on-mouse-leave on-mouse-leave} [:& component {:shape shape :frame frame @@ -311,6 +331,7 @@ #js {:shape shape :childs childs :objects objects})] + [:> frame-wrapper props])))) (defn group-container-factory diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 89f669623..f2a5da6ba 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -37,6 +37,14 @@ (when (some? e) (.-target e))) +(defn event->native-event + [^js e] + (.-nativeEvent e)) + +(defn event->browser-event + [^js e] + (.getBrowserEvent e)) + ;; --- New methods (declare get-elements-by-tag)