Merge pull request #1796 from penpot/fixed-scroll

 Add fixed position in viewer
This commit is contained in:
Andrey Antukh 2022-04-19 14:54:24 +02:00 committed by GitHub
commit 989ff8db7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 172 additions and 125 deletions

View file

@ -21,3 +21,4 @@
(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))

View file

@ -20,6 +20,7 @@
[props]
(let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")
objects (unchecked-get props "objects")
render-id (mf/use-ctx muc/render-ctx)
masked-group? (:masked-group? shape)
@ -41,12 +42,21 @@
(if masked-group?
["g" (-> (obj/new)
(obj/set! "mask" (mask-url render-id mask)))]
[mf/Fragment nil])]
[mf/Fragment nil])
;; This factory is generic, it's used for viewer, workspace and handoff.
;; These props are generated in viewer wrappers only, in the rest of the
;; cases these props will be nil, not affecting the code.
delta (unchecked-get props "delta")
fixed? (unchecked-get props "fixed?")]
[:> clip-wrapper clip-props
[:> mask-wrapper mask-props
(when masked-group?
[:> render-mask #js {:mask mask}])
[:> render-mask #js {:mask mask
:objects objects
:delta delta
:fixed? fixed?}])
(for [item childs]
[:& shape-wrapper {:shape item

View file

@ -47,20 +47,23 @@
(mf/fnc mask-shape
{::mf/wrap-props false}
[props]
(let [mask (unchecked-get props "mask")
render-id (mf/use-ctx muc/render-ctx)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
(let [mask (unchecked-get props "mask")
render-id (mf/use-ctx muc/render-ctx)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask)))
mask (cond-> mask svg-text? set-white-fill)
mask (cond-> mask svg-text? set-white-fill)
;; This factory is generic, it's used for viewer, workspace and handoff.
;; These props are generated in viewer wrappers only, in the rest of the
;; cases these props will be nil, not affecting the code.
fixed? (unchecked-get props "fixed?")
delta (unchecked-get props "delta")
mask-bb
(cond
svg-text?
(gst/position-data-points mask)
:else
(-> (gsh/transform-shape mask)
(:points)))]
mask-for-bb (-> (gsh/transform-shape mask)
(cond-> fixed? (gsh/move delta)))
mask-bb (cond
svg-text? (gst/position-data-points mask-for-bb)
:else (:points mask-for-bb))]
[:*
[:g {:opacity 0}
[:g {:id (str "shape-" (mask-id render-id mask))}

View file

@ -17,6 +17,7 @@
[app.main.fonts :as fonts]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.main.ui.shapes.filters :as filters]
@ -69,6 +70,7 @@
nav-scroll (:nav-scroll local)
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)
page-id (or page-id (-> file :data :pages first))
@ -89,6 +91,7 @@
frame (get frames index)
fullscreen? (mf/deref refs/viewer-fullscreen?)
overlays (:overlays local)
scroll (mf/use-state nil)
orig-frame
(when (:orig-frame-id current-animation)
@ -126,7 +129,11 @@
(fn [_]
(let [viewer-section (dom/get-element "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
(fn [event]
(reset! scroll (dom/get-target-scroll event)))]
(hooks/use-shortcuts ::viewer sc/shortcuts)
@ -142,9 +149,11 @@
(mf/use-effect
(fn []
(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)]
(fn []
(events/unlistenByKey key1)))))
(events/unlistenByKey key1)
(events/unlistenByKey key2)))))
(mf/use-layout-effect
(fn []
@ -258,6 +267,7 @@
:page page
:index index}]
[:section.viewer-section {:id "viewer-section"
:ref viewer-section-ref
:class (if fullscreen? "fullscreen" "")}
(cond
(empty? frames)
@ -282,79 +292,79 @@
[: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"}}
[: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
:file file
:users users
: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]
(let [size-over (calculate-size (: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-" (str (:id (:frame overlay))))
: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}]]]))]]
(for [overlay overlays]
(let [size-over (calculate-size (: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-" (str (:id (:frame overlay))))
: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}])]]]]))]]]))
;; --- Component: Viewer Page

View file

@ -7,12 +7,14 @@
(ns app.main.ui.viewer.shapes
"The main container for a frame in viewer mode"
(:require
[app.common.data :as d]
[app.common.geom.shapes :as geom]
[app.common.pages.helpers :as cph]
[app.common.spec.interactions :as cti]
[app.main.data.viewer :as dv]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.shapes.bool :as bool]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.frame :as frame]
@ -214,7 +216,8 @@
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)
@ -241,7 +244,10 @@
[:& component {:shape shape
:frame frame
:childs childs
:is-child-selected? true}]
:is-child-selected? true
:objects objects
:fixed? fixed?
:delta delta}]
[:& interaction {:shape shape
:interactions interactions
@ -250,7 +256,8 @@
;; Don't wrap svg elements inside a <g> otherwise some can break
[:& component {:shape shape
:frame frame
:childs childs}]))))
:childs childs
:objects objects}]))))
(defn frame-wrapper
[shape-container]
@ -313,11 +320,10 @@
(mf/fnc group-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
props (obj/merge! #js {} props
#js {:childs childs
:objects objects})]
(let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape")))
props (obj/merge! #js {} props
#js {:childs childs
:objects objects})]
[:> group-wrapper props]))))
(defn bool-container-factory
@ -327,8 +333,7 @@
(mf/fnc bool-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (->> (cph/get-children-ids objects (:id shape))
(let [childs (->> (cph/get-children-ids objects (:id (unchecked-get props "shape")))
(select-keys objects))
props (obj/merge! #js {} props
#js {:childs childs
@ -342,8 +347,7 @@
(mf/fnc svg-raw-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
(let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape")))
props (obj/merge! #js {} props
#js {:childs childs
:objects objects})]
@ -359,7 +363,15 @@
(mf/fnc shape-container
{::mf/wrap-props false}
[props]
(let [group-container
(let [scroll (mf/use-ctx ctx/scroll-ctx)
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")
delta {:x (/ (:scroll-left scroll) zoom) :y (/ (:scroll-top scroll) zoom)}
group-container
(mf/use-memo (mf/deps objects)
#(group-container-factory objects))
@ -369,12 +381,12 @@
svg-raw-container
(mf/use-memo (mf/deps objects)
#(svg-raw-container-factory objects))
shape (unchecked-get props "shape")
frame (unchecked-get props "frame")]
#(svg-raw-container-factory objects))]
(when (and shape (not (:hidden shape)))
(let [shape (-> (geom/transform-shape shape)
(geom/translate-to-frame frame))
(geom/translate-to-frame frame)
(cond-> fixed? (geom/move delta)))
opts #js {:shape shape
:objects objects}]
(case (:type shape)
@ -384,7 +396,7 @@
:path [:> path-wrapper opts]
:image [:> image-wrapper opts]
:circle [:> circle-wrapper opts]
:group [:> group-container {:shape shape :frame frame :objects objects}]
:group [:> group-container {:shape shape :frame frame :objects objects :fixed? fixed? :delta delta}]
:bool [:> bool-container {:shape shape :frame frame :objects objects}]
:svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}])))))))

View file

@ -46,9 +46,8 @@
in-frame? (and (some? ids)
(not= (:parent-id values) uuid/zero))
;; TODO: uncomment when fixed-scroll is fully implemented
;; first-level? (and in-frame?
;; (= (:parent-id values) (:frame-id values)))
first-level? (and in-frame?
(= (:parent-id values) (:frame-id values)))
constraints-h (or (get values :constraints-h) (gsh/default-constraints-h values))
constraints-v (or (get values :constraints-v) (gsh/default-constraints-v values))
@ -104,13 +103,11 @@
ids
#(assoc % constraint value))))))))
;; TODO: uncomment when fixed-scroll is fully implemented
;; on-fixed-scroll-clicked
;; (mf/use-callback
;; (mf/deps [ids values])
;; (fn [_]
;; (st/emit! (dch/update-shapes ids #(update % :fixed-scroll not)))))
]
on-fixed-scroll-clicked
(mf/use-callback
(mf/deps [ids values])
(fn [_]
(st/emit! (dch/update-shapes ids #(update % :fixed-scroll not)))))]
;; CONSTRAINTS
(when in-frame?
@ -168,12 +165,12 @@
[:option {:value "bottom"} (tr "workspace.options.constraints.bottom")]
[:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")]
[:option {:value "center"} (tr "workspace.options.constraints.center")]
[:option {:value "scale"} (tr "workspace.options.constraints.scale")]
;; TODO: uncomment when fixed-scroll is fully implemented
;; (when first-level?
;; [:div.row-flex
;; [:div.fix-when {:class (dom/classnames :active (:fixed-scroll values))
;; :on-click on-fixed-scroll-clicked}
;; i/pin
;; [:span (tr "workspace.options.constraints.fix-when-scrolling")]]])
]]]]]])))
[:option {:value "scale"} (tr "workspace.options.constraints.scale")]]]
(when first-level?
[:div.row-flex
[:div.fix-when {:class (dom/classnames :active (:fixed-scroll values))
:on-click on-fixed-scroll-clicked}
(if (:fixed-scroll values)
i/pin-fill
i/pin)
[:span (tr "workspace.options.constraints.fix-when-scrolling")]]])]]]])))

View file

@ -127,8 +127,18 @@
(when (some? node)
(.getAttribute ^js node attr-name)))
(defn get-scroll-position
[^js event]
(when (some? event)
{:scroll-height (.-scrollHeight event)
:scroll-left (.-scrollLeft event)
:scroll-top (.-scrollTop event)
:scroll-width (.-scrollWidth event)}))
(def get-target-val (comp get-value get-target))
(def get-target-scroll (comp get-scroll-position get-target))
(defn click
"Click a node"
[^js node]