0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-20 13:55:34 -05:00

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

@ -6,6 +6,7 @@
### :sparkles: New features ### :sparkles: New features
- Added selected colors widget in right sidebar [Taiga #2485](https://tree.taiga.io/project/penpot/us/2485) - Added selected colors widget in right sidebar [Taiga #2485](https://tree.taiga.io/project/penpot/us/2485)
- Fix elements when scrolling [Taiga #1533](https://tree.taiga.io/project/penpot/us/1533)
### :bug: Bugs fixed ### :bug: Bugs fixed
### :arrow_up: Deps updates ### :arrow_up: Deps updates

View file

@ -1,3 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292"> <svg width="12" xmlns="http://www.w3.org/2000/svg" height="12" x="767" viewBox="0 0 500 499.172" y="920.67">
<path d="M78.822.204l-3.3 3.3L56.69 22.335l2.698 9.296-17.315 17.316-18.998-12.4L0 59.624l32.865 32.865-31.33 31.33 6.598 6.599 31.33-31.33 33.001 33 23.076-23.075-12.128-18.726 17.316-17.315 9.432 2.834 22.132-22.13zm0 13.197l40.274 40.274-9.983 10.123-9.369-2.771-28.33 28.33 11.919 18.665-10.869 10.869-59.268-59.269 10.868-10.868 18.926 12.18 28.331-28.33-2.578-9.177z"/> <path d="M325.318.012c-21.828-.283-42.138 15.34-48.74 35.798-15.498 31.788-36.62 62.656-67.738 80.85-46.02 26.248-99.73 35.59-152.188 35.65-11.824-.026-24.336 1.564-34 9.065-18.02 12.488-27.52 36.96-20.133 58.034 4.15 13.85 14.838 24.125 25.197 33.582l86.5 86.5C84.488 381.137 30.993 456.543 1.26 498.187c41.623-29.727 117.01-83.215 158.632-112.942 37.542 36.62 68.373 70.025 106.956 105.56 23.195 15.987 59.377 8.335 73.035-16.723 7.6-12.733 8.225-27.804 7.5-42.23 1.366-50.12 11.25-101.404 37.675-144.67 20.658-31.714 53.59-52.978 87.635-68.023 20.972-10.427 31.76-36.413 25.57-58.77-2.664-11.118-9.64-20.31-17.897-27.873-37.25-36.595-73.2-74.495-110.7-110.84-7.227-6.593-13.718-14.767-23.268-17.904-6.682-2.644-13.898-3.908-21.08-3.758zm-.26 36.837c45.35 46.82 90.704 93.64 136.056 140.46-40.437 18.15-79.907 42.182-106.303 78.613-30.216 43.44-43.98 96.178-48.765 148.346-1.852 21.542-2.097 43.256-1.726 64.933-91.546-91.52-183.09-183.044-274.634-274.564 60.078 2.462 121.696-4.117 176.98-28.967 38.773-15.762 70.437-46.032 91.712-81.66 9.637-15.522 17.813-31.905 25.36-48.525z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1450,7 +1450,8 @@
.input-select { .input-select {
font-size: $fs12; font-size: $fs12;
margin: 0 $size-1; margin: 0 $size-1 $size-2 $size-1;
padding: 0 $size-1;
} }
svg { svg {
@ -1471,6 +1472,7 @@
.fix-when { .fix-when {
font-size: $fs12; font-size: $fs12;
cursor: pointer; cursor: pointer;
display: flex;
span { span {
margin-left: $size-2; margin-left: $size-2;

View file

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

View file

@ -20,6 +20,7 @@
[props] [props]
(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")
render-id (mf/use-ctx muc/render-ctx) render-id (mf/use-ctx muc/render-ctx)
masked-group? (:masked-group? shape) masked-group? (:masked-group? shape)
@ -41,12 +42,21 @@
(if masked-group? (if masked-group?
["g" (-> (obj/new) ["g" (-> (obj/new)
(obj/set! "mask" (mask-url render-id mask)))] (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 [:> clip-wrapper clip-props
[:> mask-wrapper mask-props [:> mask-wrapper mask-props
(when masked-group? (when masked-group?
[:> render-mask #js {:mask mask}]) [:> render-mask #js {:mask mask
:objects objects
:delta delta
:fixed? fixed?}])
(for [item childs] (for [item childs]
[:& shape-wrapper {:shape item [:& shape-wrapper {:shape item

View file

@ -47,20 +47,23 @@
(mf/fnc mask-shape (mf/fnc mask-shape
{::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-ctx)
svg-text? (and (= :text (:type mask)) (some? (:position-data mask))) 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 mask-for-bb (-> (gsh/transform-shape mask)
(cond (cond-> fixed? (gsh/move delta)))
svg-text?
(gst/position-data-points mask)
:else mask-bb (cond
(-> (gsh/transform-shape mask) svg-text? (gst/position-data-points mask-for-bb)
(:points)))] :else (:points mask-for-bb))]
[:* [:*
[:g {:opacity 0} [:g {:opacity 0}
[:g {:id (str "shape-" (mask-id render-id mask))} [:g {:id (str "shape-" (mask-id render-id mask))}

View file

@ -17,6 +17,7 @@
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.filters :as filters]
@ -69,6 +70,7 @@
nav-scroll (:nav-scroll 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) current-viewport-ref (mf/use-ref nil)
viewer-section-ref (mf/use-ref nil)
current-animation (:current-animation local) current-animation (:current-animation local)
page-id (or page-id (-> file :data :pages first)) page-id (or page-id (-> file :data :pages first))
@ -89,6 +91,7 @@
frame (get frames index) frame (get frames index)
fullscreen? (mf/deref refs/viewer-fullscreen?) fullscreen? (mf/deref refs/viewer-fullscreen?)
overlays (:overlays local) overlays (:overlays local)
scroll (mf/use-state nil)
orig-frame orig-frame
(when (:orig-frame-id current-animation) (when (:orig-frame-id current-animation)
@ -126,7 +129,11 @@
(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
(fn [event]
(reset! scroll (dom/get-target-scroll event)))]
(hooks/use-shortcuts ::viewer sc/shortcuts) (hooks/use-shortcuts ::viewer sc/shortcuts)
@ -142,9 +149,11 @@
(mf/use-effect (mf/use-effect
(fn [] (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 [] (fn []
(events/unlistenByKey key1))))) (events/unlistenByKey key1)
(events/unlistenByKey key2)))))
(mf/use-layout-effect (mf/use-layout-effect
(fn [] (fn []
@ -258,6 +267,7 @@
:page page :page page
:index index}] :index index}]
[:section.viewer-section {:id "viewer-section" [:section.viewer-section {:id "viewer-section"
:ref viewer-section-ref
:class (if fullscreen? "fullscreen" "")} :class (if fullscreen? "fullscreen" "")}
(cond (cond
(empty? frames) (empty? frames)
@ -282,79 +292,79 @@
[: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
[:*
(when orig-frame
[:div.viewport-container
{:ref orig-viewport-ref
:style {:width (:width orig-size)
:height (:height orig-size)
:position "relative"}}
[:div.viewer-clipper [:& interactions/viewport
[:* {:frame orig-frame
(when orig-frame :base-frame orig-frame
[:div.viewport-container :frame-offset (gpt/point 0 0)
{:ref orig-viewport-ref :size orig-size
:style {:width (:width orig-size) :page page
:height (:height orig-size) :file file
:position "relative"}} :users users
:interactions-mode :hide}]])
[:& interactions/viewport [:div.viewport-container
{:frame orig-frame {:ref current-viewport-ref
:base-frame orig-frame :style {:width (:width size)
:frame-offset (gpt/point 0 0) :height (:height size)
:size orig-size :position "relative"}}
:page page
:file file
:users users
:interactions-mode :hide}]])
[:div.viewport-container [:& interactions/viewport
{:ref current-viewport-ref {:frame frame
:style {:width (:width size) :base-frame frame
:height (:height size) :frame-offset (gpt/point 0 0)
:position "relative"}} :size size
:page page
:file file
:users users
:interactions-mode interactions-mode}]
[:& interactions/viewport (for [overlay overlays]
{:frame frame (let [size-over (calculate-size (:frame overlay) zoom)]
:base-frame frame [:*
:frame-offset (gpt/point 0 0) (when (or (:close-click-outside overlay)
:size size (:background-overlay overlay))
:page page [:div.viewer-overlay-background
:file file {:class (dom/classnames
:users users :visible (:background-overlay overlay))
:interactions-mode interactions-mode}] :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] (when (= section :comments)
(let [size-over (calculate-size (:frame overlay) zoom)] [:& comments-layer {:file file
[:* :users users
(when (or (:close-click-outside overlay) :frame frame
(:background-overlay overlay)) :page page
[:div.viewer-overlay-background :zoom zoom}])]]]]))]]]))
{: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}])]]]))]]]))
;; --- Component: Viewer Page ;; --- Component: Viewer Page

View file

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

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

View file

@ -127,8 +127,18 @@
(when (some? node) (when (some? node)
(.getAttribute ^js node attr-name))) (.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-val (comp get-value get-target))
(def get-target-scroll (comp get-scroll-position get-target))
(defn click (defn click
"Click a node" "Click a node"
[^js node] [^js node]