From b68fdee94617b57877a33dfbef8783ae8ff90ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 30 Mar 2022 15:31:42 +0200 Subject: [PATCH] :sparkles: Add fixed position in viewer --- CHANGES.md | 1 + frontend/resources/images/icons/pin.svg | 5 +- .../partials/sidebar-element-options.scss | 4 +- frontend/src/app/main/ui/context.cljs | 1 + frontend/src/app/main/ui/shapes/group.cljs | 14 +- frontend/src/app/main/ui/shapes/mask.cljs | 27 ++-- frontend/src/app/main/ui/viewer.cljs | 152 ++++++++++-------- frontend/src/app/main/ui/viewer/shapes.cljs | 48 +++--- .../sidebar/options/menus/constraints.cljs | 35 ++-- frontend/src/app/util/dom.cljs | 10 ++ 10 files changed, 172 insertions(+), 125 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 23f1e7280..fce43a342 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ ### :sparkles: New features - 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 ### :arrow_up: Deps updates diff --git a/frontend/resources/images/icons/pin.svg b/frontend/resources/images/icons/pin.svg index 46d71436a..0194c6401 100644 --- a/frontend/resources/images/icons/pin.svg +++ b/frontend/resources/images/icons/pin.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 58ff19fc0..f678044e1 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1450,7 +1450,8 @@ .input-select { font-size: $fs12; - margin: 0 $size-1; + margin: 0 $size-1 $size-2 $size-1; + padding: 0 $size-1; } svg { @@ -1471,6 +1472,7 @@ .fix-when { font-size: $fs12; cursor: pointer; + display: flex; span { margin-left: $size-2; diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 4fd5d6a1f..634739944 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -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)) \ No newline at end of file diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index 083d658e8..403a9f2a0 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -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 diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index 9a0470901..5e3fd46af 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -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))} diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 3bb8ad2c9..dc28de25d 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -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 diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index c21622409..99a7e11dc 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -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 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}]))))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs index 6616503ab..517df4c69 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs @@ -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")]]])]]]]))) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 1c1b4854f..f0eaf40b3 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -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]