🎉 Add animations to interactions

This commit is contained in:
Andrés Moya 2021-10-29 10:43:53 +02:00 committed by Andrey Antukh
parent 24062beebe
commit 81cbc33dbb
24 changed files with 1479 additions and 160 deletions

View file

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[app.main.constants :as c]
[app.main.data.comments :as dcm]
@ -316,6 +317,12 @@
(update [_ state]
(d/dissoc-in state [:viewer-local :nav-scroll]))))
(defn complete-animation
[]
(ptk/reify ::complete-animation
ptk/UpdateEvent
(update [_ state]
(d/dissoc-in state [:viewer-local :current-animation]))))
;; --- Navigation inside page
@ -335,23 +342,38 @@
(rx/of (rt/nav screen pparams (assoc qparams :index index)))))))
(defn go-to-frame
[frame-id]
(us/verify ::us/uuid frame-id)
(ptk/reify ::go-to-frame
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-local :overlays] []))
([frame-id] (go-to-frame frame-id nil))
([frame-id animation]
(us/verify ::us/uuid frame-id)
(us/verify (s/nilable ::cti/animation) animation)
(ptk/reify ::go-to-frame
ptk/UpdateEvent
(update [_ state]
(let [route (:route state)
qparams (:query-params route)
page-id (:page-id qparams)
index (:index qparams)
frames (get-in state [:viewer :pages page-id :frames])
frame (get frames index)]
(cond-> state
:always
(assoc-in [:viewer-local :overlays] [])
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
qparams (:query-params route)
page-id (:page-id qparams)
(some? animation)
(assoc-in [:viewer-local :current-animation]
{:kind :go-to-frame
:orig-frame-id (:id frame)
:animation animation}))))
frames (get-in state [:viewer :pages page-id :frames])
index (d/index-of-pred frames #(= (:id %) frame-id))]
(when index
(rx/of (go-to-frame-by-index index)))))))
ptk/WatchEvent
(watch [_ state _]
(let [route (:route state)
qparams (:query-params route)
page-id (:page-id qparams)
frames (get-in state [:viewer :pages page-id :frames])
index (d/index-of-pred frames #(= (:id %) frame-id))]
(when index
(rx/of (go-to-frame-by-index index))))))))
(defn go-to-frame-auto
[]
@ -383,12 +405,39 @@
;; --- Overlays
(defn- do-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})))
(defn- do-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})))
(defn open-overlay
[frame-id position close-click-outside background-overlay]
[frame-id position close-click-outside background-overlay animation]
(us/verify ::us/uuid frame-id)
(us/verify ::us/point position)
(us/verify (s/nilable ::us/boolean) close-click-outside)
(us/verify (s/nilable ::us/boolean) background-overlay)
(us/verify (s/nilable ::cti/animation) animation)
(ptk/reify ::open-overlay
ptk/UpdateEvent
(update [_ state]
@ -399,19 +448,21 @@
frame (d/seek #(= (:id %) frame-id) frames)
overlays (get-in state [:viewer-local :overlays])]
(if-not (some #(= (:frame %) frame) overlays)
(update-in state [:viewer-local :overlays] conj
{:frame frame
:position position
:close-click-outside close-click-outside
:background-overlay background-overlay})
(do-open-overlay state
frame
position
close-click-outside
background-overlay
animation)
state)))))
(defn toggle-overlay
[frame-id position close-click-outside background-overlay]
[frame-id position close-click-outside background-overlay animation]
(us/verify ::us/uuid frame-id)
(us/verify ::us/point position)
(us/verify (s/nilable ::us/boolean) close-click-outside)
(us/verify (s/nilable ::us/boolean) background-overlay)
(us/verify (s/nilable ::cti/animation) animation)
(ptk/reify ::toggle-overlay
ptk/UpdateEvent
(update [_ state]
@ -422,23 +473,27 @@
frame (d/seek #(= (:id %) frame-id) frames)
overlays (get-in state [:viewer-local :overlays])]
(if-not (some #(= (:frame %) frame) overlays)
(update-in state [:viewer-local :overlays] conj
{:frame frame
:position position
:close-click-outside close-click-outside
:background-overlay background-overlay})
(update-in state [:viewer-local :overlays]
(fn [overlays]
(d/removev #(= (:id (:frame %)) frame-id) overlays))))))))
(do-open-overlay state
frame
position
close-click-outside
background-overlay
animation)
(do-close-overlay state
(:id frame)
(cti/invert-direction animation)))))))
(defn close-overlay
[frame-id]
(ptk/reify ::close-overlay
ptk/UpdateEvent
(update [_ state]
(update-in state [:viewer-local :overlays]
(fn [overlays]
(d/removev #(= (:id (:frame %)) frame-id) overlays))))))
([frame-id] (close-overlay frame-id nil))
([frame-id animation]
(us/verify ::us/uuid frame-id)
(us/verify (s/nilable ::cti/animation) animation)
(ptk/reify ::close-overlay
ptk/UpdateEvent
(update [_ state]
(do-close-overlay state
frame-id
animation)))))
;; --- Objects selection

View file

@ -17,6 +17,10 @@
(def align-middle (icon-xref :align-middle))
(def align-top (icon-xref :align-top))
(def alignment (icon-xref :alignment))
(def animate-down (icon-xref :animate-down))
(def animate-left (icon-xref :animate-left))
(def animate-right (icon-xref :animate-right))
(def animate-up (icon-xref :animate-up))
(def arrow-down (icon-xref :arrow-down))
(def arrow-end (icon-xref :arrow-end))
(def arrow-slide (icon-xref :arrow-slide))
@ -42,6 +46,11 @@
(def copy (icon-xref :copy))
(def curve (icon-xref :curve))
(def download (icon-xref :download))
(def easing-linear (icon-xref :easing-linear))
(def easing-ease (icon-xref :easing-ease))
(def easing-ease-in (icon-xref :easing-ease-in))
(def easing-ease-out (icon-xref :easing-ease-out))
(def easing-ease-in-out (icon-xref :easing-ease-in-out))
(def exit (icon-xref :exit))
(def export (icon-xref :export))
(def eye (icon-xref :eye))

View file

@ -6,6 +6,7 @@
(ns app.main.ui.viewer
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
[app.main.data.comments :as dcm]
@ -31,9 +32,22 @@
(defn- calculate-size
[frame zoom]
(let [{:keys [_ _ width height]} (filters/get-filters-bounds frame)]
{:width (* width zoom)
:height (* height zoom)
:vbox (str "0 0 " width " " height)}))
{:base-width width
:base-height height
:width (* width zoom)
:height (* height zoom)
:vbox (str "0 0 " width " " height)}))
(defn- calculate-wrapper
[size1 size2 zoom]
(cond
(nil? size1) size2
(nil? size2) size1
:else (let [width (max (:base-width size1) (:base-width size2))
height (max (:base-height size1) (:base-height size2))]
{:width (* width zoom)
:height (* height zoom)
:vbox (str "0 0 " width " " height)})))
(mf/defc viewer
[{:keys [params data]}]
@ -41,24 +55,41 @@
(let [{:keys [page-id section index]} params
{:keys [file users project permissions]} data
local (mf/deref refs/viewer-local)
local (mf/deref refs/viewer-local)
nav-scroll (:nav-scroll local)
orig-viewport-ref (mf/use-ref nil)
current-viewport-ref (mf/use-ref nil)
current-animation (:current-animation local)
page-id (or page-id (-> file :data :pages first))
page (mf/use-memo
(mf/deps data page-id)
(fn []
(get-in data [:pages page-id])))
page (mf/use-memo
(mf/deps data page-id)
(fn []
(get-in data [:pages page-id])))
zoom (:zoom local)
frames (:frames page)
frame (get frames index)
zoom (:zoom local)
frames (:frames page)
frame (get frames index)
size (mf/use-memo
(mf/deps frame zoom)
(fn [] (calculate-size frame zoom)))
overlays (:overlays local)
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 frame zoom)))
orig-size (mf/use-memo
(mf/deps orig-frame zoom)
(fn [] (when orig-frame (calculate-size orig-frame zoom))))
wrapper-size (mf/use-memo
(mf/deps size orig-size zoom)
(fn [] (calculate-wrapper size orig-size zoom)))
interactions-mode
(:interactions-mode local)
@ -96,11 +127,67 @@
(mf/use-layout-effect
(mf/deps nav-scroll)
(fn []
;; Set scroll position after navigate
(when (number? nav-scroll)
(let [viewer-section (dom/get-element "viewer-section")]
(st/emit! (dv/reset-nav-scroll))
(dom/set-scroll-pos! viewer-section nav-scroll)))))
(mf/use-layout-effect
(mf/deps index)
(fn []
;; Navigate animation needs to be started after navigation
;; is complete, and we have the next page index.
(when (and current-animation
(= (:kind current-animation) :go-to-frame))
(let [orig-viewport (mf/ref-val orig-viewport-ref)
current-viewport (mf/ref-val current-viewport-ref)]
(interactions/animate-go-to-frame
(:animation current-animation)
current-viewport
orig-viewport
size
orig-size
wrapper-size)))))
(mf/use-layout-effect
(mf/deps current-animation)
(fn []
;; Overlay animations may be started when needed.
(when current-animation
(case (:kind current-animation)
:open-overlay
(let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation))))
overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation))
overlays)
overlay-size (calculate-size (:frame overlay) zoom)
overlay-position {:x (* (:x (:position overlay)) zoom)
:y (* (:y (:position overlay)) zoom)}]
(interactions/animate-open-overlay
(:animation current-animation)
overlay-viewport
wrapper-size
overlay-size
overlay-position))
:close-overlay
(let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation))))
overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation))
overlays)
overlay-size (calculate-size (:frame overlay) zoom)
overlay-position {:x (* (:x (:position overlay)) zoom)
:y (* (:y (:position overlay)) zoom)}]
(interactions/animate-close-overlay
(:animation current-animation)
overlay-viewport
wrapper-size
overlay-size
overlay-position
(:id (:frame overlay))))
nil))))
[:div {:class (dom/classnames
:force-visible (:show-thumbnails local)
:viewer-layout (not= section :handoff)
@ -139,57 +226,81 @@
:section section
:local local}]
[:div.viewport-container
{:style {:width (:width size)
:height (:height size)
:position "relative"}}
[:*
[:div.viewer-wrapper
{:style {:width (:width wrapper-size)
:height (:height wrapper-size)}}
(when (= section :comments)
[:& comments-layer {:file file
:users users
:frame frame
:page page
:zoom zoom}])
(when orig-frame
[:div.viewport-container
{:ref orig-viewport-ref
:style {:width (:width orig-size)
:height (:height orig-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 orig-frame
:base-frame orig-frame
:frame-offset (gpt/point 0 0)
:size orig-size
:page page
:file file
:users users
:interactions-mode :hide}]])
(for [overlay (:overlays local)]
(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 frame)
:height (:height frame)
:position "absolute"
:left 0
:top 0}
:on-click #(when (:close-click-outside overlay)
(close-overlay (:frame overlay)))}])
[:div.viewport-container.viewer-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}]]]))]))]]]))
[:div.viewport-container
{:ref current-viewport-ref
:style {:width (:width size)
:height (:height size)
:position "relative"}
}
(when (= section :comments)
[:& comments-layer {:file file
:users users
:frame frame
:page page
:zoom zoom}])
[:& 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}]]]))]]]))]]]))
;; --- Component: Viewer Page

View file

@ -169,3 +169,338 @@
[:span.icon i/tick]
[:span.label (tr "viewer.header.show-interactions-on-click")]]]]]))
(defn animate-go-to-frame
[animation current-viewport orig-viewport current-size orig-size wrapper-size]
(case (:animation-type animation)
:dissolve
(do (dom/animate! orig-viewport
[#js {:opacity "100"}
#js {:opacity "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(dom/animate! current-viewport
[#js {:opacity "0"}
#js {:opacity "100"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}))
:slide
(case (:way animation)
:in
(case (:direction animation)
:right
(let [offset (+ (:width current-size)
(/ (- (:width wrapper-size) (:width current-size)) 2))]
(dom/animate! current-viewport
[#js {:left (str "-" offset "px")}
#js {:left "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! orig-viewport
[#js {:left "0"
:opacity "100%"}
#js {:left (str (* offset 0.2) "px")
:opacity "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))
:left
(let [offset (+ (:width current-size)
(/ (- (:width wrapper-size) (:width current-size)) 2))]
(dom/animate! current-viewport
[#js {:right (str "-" offset "px")}
#js {:right "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! orig-viewport
[#js {:right "0"
:opacity "100%"}
#js {:right (str (* offset 0.2) "px")
:opacity "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))
:up
(let [offset (+ (:height current-size)
(/ (- (:height wrapper-size) (:height current-size)) 2))]
(dom/animate! current-viewport
[#js {:bottom (str "-" offset "px")}
#js {:bottom "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! orig-viewport
[#js {:bottom "0"
:opacity "100%"}
#js {:bottom (str (* offset 0.2) "px")
:opacity "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))
:down
(let [offset (+ (:height current-size)
(/ (- (:height wrapper-size) (:height current-size)) 2))]
(dom/animate! current-viewport
[#js {:top (str "-" offset "px")}
#js {:top "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! orig-viewport
[#js {:top "0"
:opacity "100%"}
#js {:top (str (* offset 0.2) "px")
:opacity "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}))))
:out
(case (:direction animation)
:right
(let [offset (+ (:width orig-size)
(/ (- (:width wrapper-size) (:width orig-size)) 2))]
(dom/set-css-property! orig-viewport "z-index" 10000)
(dom/animate! orig-viewport
[#js {:right "0"}
#js {:right (str "-" offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! current-viewport
[#js {:right (str (* offset 0.2) "px")
:opacity "0"}
#js {:right "0"
:opacity "100%"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))
:left
(let [offset (+ (:width orig-size)
(/ (- (:width wrapper-size) (:width orig-size)) 2))]
(dom/set-css-property! orig-viewport "z-index" 10000)
(dom/animate! orig-viewport
[#js {:left "0"}
#js {:left (str "-" offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! current-viewport
[#js {:left (str (* offset 0.2) "px")
:opacity "0"}
#js {:left "0"
:opacity "100%"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))
:up
(let [offset (+ (:height orig-size)
(/ (- (:height wrapper-size) (:height orig-size)) 2))]
(dom/set-css-property! orig-viewport "z-index" 10000)
(dom/animate! orig-viewport
[#js {:top "0"}
#js {:top (str "-" offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! current-viewport
[#js {:top (str (* offset 0.2) "px")
:opacity "0"}
#js {:top "0"
:opacity "100%"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))
:down
(let [offset (+ (:height orig-size)
(/ (- (:height wrapper-size) (:height orig-size)) 2))]
(dom/set-css-property! orig-viewport "z-index" 10000)
(dom/animate! orig-viewport
[#js {:bottom "0"}
#js {:bottom (str "-" offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(when (:offset-effect animation)
(dom/animate! current-viewport
[#js {:bottom (str (* offset 0.2) "px")
:opacity "0"}
#js {:bottom "0"
:opacity "100%"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))))
:push
(case (:direction animation)
:right
(let [offset (:width wrapper-size)]
(dom/animate! current-viewport
[#js {:left (str "-" offset "px")}
#js {:left "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(dom/animate! orig-viewport
[#js {:left "0"}
#js {:left (str offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}))
:left
(let [offset (:width wrapper-size)]
(dom/animate! current-viewport
[#js {:right (str "-" offset "px")}
#js {:right "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(dom/animate! orig-viewport
[#js {:right "0"}
#js {:right (str offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}))
:up
(let [offset (:height wrapper-size)]
(dom/animate! current-viewport
[#js {:bottom (str "-" offset "px")}
#js {:bottom "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(dom/animate! orig-viewport
[#js {:bottom "0"}
#js {:bottom (str offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}))
:down
(let [offset (:height wrapper-size)]
(dom/animate! current-viewport
[#js {:top (str "-" offset "px")}
#js {:top "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
(dom/animate! orig-viewport
[#js {:top "0"}
#js {:top (str offset "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))})))))
(defn animate-open-overlay
[animation overlay-viewport
wrapper-size overlay-size overlay-position]
(case (:animation-type animation)
:dissolve
(dom/animate! overlay-viewport
[#js {:opacity "0"}
#js {:opacity "100"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
:slide
(case (:direction animation) ;; way and offset-effect are ignored
:right
(dom/animate! overlay-viewport
[#js {:left (str "-" (:width overlay-size) "px")}
#js {:left (str (:x overlay-position) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
:left
(dom/animate! overlay-viewport
[#js {:left (str (:width wrapper-size) "px")}
#js {:left (str (:x overlay-position) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
:up
(dom/animate! overlay-viewport
[#js {:top (str (:height wrapper-size) "px")}
#js {:top (str (:y overlay-position) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)))
:down
(dom/animate! overlay-viewport
[#js {:top (str "-" (:height overlay-size) "px")}
#js {:top (str (:y overlay-position) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation))))))
(defn animate-close-overlay
[animation overlay-viewport
wrapper-size overlay-size overlay-position overlay-id]
(case (:animation-type animation)
:dissolve
(dom/animate! overlay-viewport
[#js {:opacity "100"}
#js {:opacity "0"}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)
(dv/close-overlay overlay-id)))
:slide
(case (:direction animation) ;; way and offset-effect are ignored
:right
(dom/animate! overlay-viewport
[#js {:left (str (:x overlay-position) "px")}
#js {:left (str (:width wrapper-size) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)
(dv/close-overlay overlay-id)))
:left
(dom/animate! overlay-viewport
[#js {:left (str (:x overlay-position) "px")}
#js {:left (str "-" (:width overlay-size) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)
(dv/close-overlay overlay-id)))
:up
(dom/animate! overlay-viewport
[#js {:top (str (:y overlay-position) "px")}
#js {:top (str "-" (:height overlay-size) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)
(dv/close-overlay overlay-id)))
:down
(dom/animate! overlay-viewport
[#js {:top (str (:y overlay-position) "px")}
#js {:top (str (:height wrapper-size) "px")}]
#js {:duration (:duration animation)
:easing (name (:easing animation))}
#(st/emit! (dv/complete-animation)
(dv/close-overlay overlay-id))))))

View file

@ -38,7 +38,7 @@
(def viewer-interactions-show?
(l/derived :interactions-show? refs/viewer-local))
(defn activate-interaction
(defn- activate-interaction
[interaction shape base-frame frame-offset objects]
(case (:action-type interaction)
:navigate
@ -48,7 +48,7 @@
(dom/get-scroll-pos viewer-section)
0)]
(st/emit! (dv/set-nav-scroll scroll)
(dv/go-to-frame frame-id))))
(dv/go-to-frame frame-id (:animation interaction)))))
:open-overlay
(let [dest-frame-id (:destination interaction)
@ -64,7 +64,8 @@
(st/emit! (dv/open-overlay dest-frame-id
position
close-click-outside
background-overlay))))
background-overlay
(:animation interaction)))))
:toggle-overlay
(let [frame-id (:destination interaction)
@ -75,14 +76,15 @@
(st/emit! (dv/toggle-overlay frame-id
position
close-click-outside
background-overlay))))
background-overlay
(:animation interaction)))))
:close-overlay
(let [frame-id (or (:destination interaction)
(if (= (:type shape) :frame)
(:id shape)
(:frame-id shape)))]
(st/emit! (dv/close-overlay frame-id)))
(st/emit! (dv/close-overlay frame-id (:animation interaction))))
:prev-screen
(st/emit! (rt/nav-back-local))
@ -93,7 +95,7 @@
nil))
;; Perform the opposite action of an interaction, if possible
(defn deactivate-interaction
(defn- deactivate-interaction
[interaction shape base-frame frame-offset objects]
(case (:action-type interaction)
:open-overlay
@ -112,7 +114,8 @@
(st/emit! (dv/toggle-overlay frame-id
position
close-click-outside
background-overlay))))
background-overlay
(:animation interaction)))))
:close-overlay
(let [dest-frame-id (:destination interaction)
@ -128,10 +131,11 @@
(st/emit! (dv/open-overlay dest-frame-id
position
close-click-outside
background-overlay))))
background-overlay
(:animation interaction)))))
nil))
(defn on-mouse-down
(defn- on-mouse-down
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(or (= (:event-type %) :click)
@ -141,7 +145,7 @@
(doseq [interaction interactions]
(activate-interaction interaction shape base-frame frame-offset objects)))))
(defn on-mouse-up
(defn- on-mouse-up
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(= (:event-type %) :mouse-press)))]
@ -150,7 +154,7 @@
(doseq [interaction interactions]
(deactivate-interaction interaction shape base-frame frame-offset objects)))))
(defn on-mouse-enter
(defn- on-mouse-enter
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(or (= (:event-type %) :mouse-enter)
@ -160,7 +164,7 @@
(doseq [interaction interactions]
(activate-interaction interaction shape base-frame frame-offset objects)))))
(defn on-mouse-leave
(defn- on-mouse-leave
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(= (:event-type %) :mouse-leave)))
@ -173,7 +177,7 @@
(doseq [interaction interactions-inv]
(deactivate-interaction interaction shape base-frame frame-offset objects)))))
(defn on-load
(defn- on-load
[shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(= (:event-type %) :after-delay)))]

View file

@ -73,6 +73,23 @@
:bottom-right (tr "workspace.options.interaction-pos-bottom-right")
:bottom-center (tr "workspace.options.interaction-pos-bottom-center")})
(defn- animation-type-names
[interaction]
(cond->
{:dissolve (tr "workspace.options.interaction-animation-dissolve")
:slide (tr "workspace.options.interaction-animation-slide")}
(cti/allow-push? (:action-type interaction))
(assoc :push (tr "workspace.options.interaction-animation-push"))))
(defn- easing-names
[]
{:linear (tr "workspace.options.interaction-easing-linear")
:ease (tr "workspace.options.interaction-easing-ease")
:ease-in (tr "workspace.options.interaction-easing-ease-in")
:ease-out (tr "workspace.options.interaction-easing-ease-out")
:ease-in-out (tr "workspace.options.interaction-easing-ease-in-out")})
(def flow-for-rename-ref
(l/derived (l/in [:workspace-local :flow-for-rename]) st/state))
@ -170,10 +187,13 @@
close-click-outside? (:close-click-outside interaction false)
background-overlay? (:background-overlay interaction false)
preserve-scroll? (:preserve-scroll interaction false)
way (-> interaction :animation :way)
direction (-> interaction :animation :direction)
extended-open? (mf/use-state false)
ext-delay-ref (mf/use-ref nil)
ext-duration-ref (mf/use-ref nil)
select-text
(fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref))))
@ -237,7 +257,36 @@
change-background-overlay
(fn [event]
(let [value (-> event dom/get-target dom/checked?)]
(update-interaction index #(cti/set-background-overlay % value))))]
(update-interaction index #(cti/set-background-overlay % value))))
change-animation-type
(fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
(update-interaction index #(cti/set-animation-type % value))))
change-duration
(fn [value]
(update-interaction index #(cti/set-duration % value)))
change-easing
(fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
(update-interaction index #(cti/set-easing % value))))
change-way
(fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
(update-interaction index #(cti/set-way % value))))
change-direction
(fn [value]
(update-interaction index #(cti/set-direction % value)))
change-offset-effect
(fn [event]
(let [value (-> event dom/get-target dom/checked?)]
(update-interaction index #(cti/set-offset-effect % value))))
]
[:*
[:div.element-set-options-group {:class (dom/classnames
@ -382,7 +431,97 @@
:checked background-overlay?
:on-change change-background-overlay}]
[:label {:for (str "background-" index)}
(tr "workspace.options.interaction-background")]]]])])]]))
(tr "workspace.options.interaction-background")]]]])
; Animation select
[:div.interactions-element.separator
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-animation")]
[:select.input-select
{:value (str (-> interaction :animation :animation-type))
:on-change change-animation-type}
[:option {:value ""} (tr "workspace.options.interaction-animation-none")]
(for [[value name] (animation-type-names interaction)]
[:option {:value (str value)} name])]]
; Direction
(when (cti/has-way? interaction)
[:div.interactions-element.interactions-way-buttons
[:div.input-radio
[:input {:type "radio"
:id "way-in"
:checked (= :in way)
:name "animation-way"
:value ":in"
:on-change change-way}]
[:label {:for "way-in"} (tr "workspace.options.interaction-in")]]
[:div.input-radio
[:input {:type "radio"
:id "way-out"
:checked (= :out way)
:name "animation-way"
:value ":out"
:on-change change-way}]
[:label {:for "way-out"} (tr "workspace.options.interaction-out")]]])
; Direction
(when (cti/has-direction? interaction)
[:div.interactions-element.interactions-direction-buttons
[:div.element-set-actions-button
{:class (dom/classnames :active (= direction :right))
:on-click #(change-direction :right)}
i/animate-right]
[:div.element-set-actions-button
{:class (dom/classnames :active (= direction :down))
:on-click #(change-direction :down)}
i/animate-down]
[:div.element-set-actions-button
{:class (dom/classnames :active (= direction :left))
:on-click #(change-direction :left)}
i/animate-left]
[:div.element-set-actions-button
{:class (dom/classnames :active (= direction :up))
:on-click #(change-direction :up)}
i/animate-up]])
; Duration
(when (cti/has-duration? interaction)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-duration")]
[:div.input-element {:title (tr "workspace.options.interaction-ms")}
[:> numeric-input {:ref ext-duration-ref
:on-click (select-text ext-duration-ref)
:on-change change-duration
:value (-> interaction :animation :duration)
:title (tr "workspace.options.interaction-ms")}]
[:span.after (tr "workspace.options.interaction-ms")]]])
; Easing
(when (cti/has-easing? interaction)
[:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-easing")]
[:select.input-select
{:value (str (-> interaction :animation :easing))
:on-change change-easing}
(for [[value name] (easing-names)]
[:option {:value (str value)} name])]
[:div.interactions-easing-icon
(case (-> interaction :animation :easing)
:linear i/easing-linear
:ease i/easing-ease
:ease-in i/easing-ease-in
:ease-out i/easing-ease-out
:ease-in-out i/easing-ease-in-out)]])
; Offset effect
(when (cti/has-offset-effect? interaction)
[:div.interactions-element
[:div.input-checkbox
[:input {:type "checkbox"
:id (str "offset-effect-" index)
:checked (-> interaction :animation :offset-effect)
:on-change change-offset-effect}]
[:label {:for (str "offset-effect-" index)}
(tr "workspace.options.interaction-offset-effect")]]])])]]))
(mf/defc interactions-menu
[{:keys [shape] :as props}]

View file

@ -425,3 +425,10 @@
[]
(.back (.-history js/window)))
(defn animate!
([item keyframes duration] (animate! item keyframes duration nil))
([item keyframes duration onfinish]
(let [animation (.animate item keyframes duration)]
(when onfinish
(set! (.-onfinish animation) onfinish)))))