2020-03-18 09:52:13 +01:00
|
|
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
;;
|
2021-04-10 09:43:04 +02:00
|
|
|
;; Copyright (c) UXBOX Labs S.L
|
2020-03-18 09:52:13 +01:00
|
|
|
|
2020-08-18 19:26:37 +02:00
|
|
|
(ns app.main.ui.hooks
|
2020-03-18 09:52:13 +01:00
|
|
|
"A collection of general purpose react hooks."
|
|
|
|
(:require
|
2022-02-23 16:30:01 +01:00
|
|
|
[app.common.pages :as cp]
|
2021-04-22 14:06:11 +02:00
|
|
|
[app.main.data.shortcuts :as dsc]
|
2022-02-23 16:30:01 +01:00
|
|
|
[app.main.refs :as refs]
|
2021-05-12 10:21:53 +02:00
|
|
|
[app.main.store :as st]
|
2020-08-18 19:26:37 +02:00
|
|
|
[app.util.dom :as dom]
|
|
|
|
[app.util.dom.dnd :as dnd]
|
|
|
|
[app.util.timers :as ts]
|
2021-02-01 17:47:54 +01:00
|
|
|
[beicon.core :as rx]
|
2021-06-17 16:29:52 +02:00
|
|
|
[rumext.alpha :as mf]))
|
2020-03-18 09:52:13 +01:00
|
|
|
|
|
|
|
(defn use-rxsub
|
|
|
|
[ob]
|
|
|
|
(let [[state reset-state!] (mf/useState @ob)]
|
|
|
|
(mf/useEffect
|
|
|
|
(fn []
|
|
|
|
(let [sub (rx/subscribe ob #(reset-state! %))]
|
|
|
|
#(rx/cancel! sub)))
|
|
|
|
#js [ob])
|
|
|
|
state))
|
2020-04-02 20:01:49 +02:00
|
|
|
|
|
|
|
(defn use-shortcuts
|
2021-05-12 10:21:53 +02:00
|
|
|
[key shortcuts]
|
2020-04-02 20:01:49 +02:00
|
|
|
(mf/use-effect
|
2021-05-12 10:21:53 +02:00
|
|
|
#js [(str key) shortcuts]
|
2020-04-02 20:01:49 +02:00
|
|
|
(fn []
|
2021-05-12 10:21:53 +02:00
|
|
|
(st/emit! (dsc/push-shortcuts key shortcuts))
|
|
|
|
(fn []
|
|
|
|
(st/emit! (dsc/pop-shortcuts key))))))
|
2020-04-02 20:01:49 +02:00
|
|
|
|
2020-04-10 14:30:06 +02:00
|
|
|
(defn invisible-image
|
|
|
|
[]
|
|
|
|
(let [img (js/Image.)
|
|
|
|
imd "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="]
|
|
|
|
(set! (.-src img) imd)
|
|
|
|
img))
|
|
|
|
|
2020-05-25 10:01:57 +02:00
|
|
|
(defn- set-timer
|
|
|
|
[state ms func]
|
|
|
|
(assoc state :timer (ts/schedule ms func)))
|
|
|
|
|
|
|
|
(defn- cancel-timer
|
|
|
|
[state]
|
|
|
|
(let [timer (:timer state)]
|
|
|
|
(if timer
|
|
|
|
(do
|
|
|
|
(rx/dispose! timer)
|
|
|
|
(dissoc state :timer))
|
|
|
|
state)))
|
|
|
|
|
2020-06-19 12:06:22 +02:00
|
|
|
(def sortable-ctx (mf/create-context nil))
|
|
|
|
|
|
|
|
(mf/defc sortable-container
|
|
|
|
[{:keys [children] :as props}]
|
|
|
|
(let [global-drag-end (mf/use-memo #(rx/subject))]
|
|
|
|
[:& (mf/provider sortable-ctx) {:value global-drag-end}
|
|
|
|
children]))
|
|
|
|
|
|
|
|
|
|
|
|
;; The dnd API is problematic for nested elements, such a sortable items tree.
|
|
|
|
;; The approach used here to solve bad situations is:
|
|
|
|
;; - Capture all events in the leaf draggable elements, and stop propagation.
|
|
|
|
;; - Ignore events originated in non-draggable children.
|
|
|
|
;; - At drag operation end, all elements that have received some enter/over
|
|
|
|
;; event and have not received the corresponding leave event, are notified
|
2020-06-19 15:20:51 +02:00
|
|
|
;; so they can clean up. This can be occur, for example, if
|
|
|
|
;; * some leave events are throttled out because of a slow computer
|
|
|
|
;; * some corner cases of mouse entering a container element, and then
|
|
|
|
;; moving into a contained element. This is anyway mitigated by not
|
|
|
|
;; stopping propagation of leave event.
|
2020-06-19 12:06:22 +02:00
|
|
|
;;
|
|
|
|
;; Do not remove commented out lines, they are useful to debug events when
|
|
|
|
;; things go weird.
|
|
|
|
|
2020-04-10 14:30:06 +02:00
|
|
|
(defn use-sortable
|
2021-03-29 12:56:51 +02:00
|
|
|
[& {:keys [data-type data on-drop on-drag on-hold disabled detect-center?] :as opts}]
|
2020-04-10 14:30:06 +02:00
|
|
|
(let [ref (mf/use-ref)
|
2020-05-25 10:01:57 +02:00
|
|
|
state (mf/use-state {:over nil
|
2020-06-19 12:06:22 +02:00
|
|
|
:timer nil
|
|
|
|
:subscr nil})
|
|
|
|
|
|
|
|
global-drag-end (mf/use-ctx sortable-ctx)
|
|
|
|
|
|
|
|
cleanup
|
|
|
|
(fn []
|
2021-06-17 16:29:52 +02:00
|
|
|
(some-> (:subscr @state) rx/unsub!)
|
2020-06-19 12:06:22 +02:00
|
|
|
(swap! state (fn [state]
|
2022-01-12 14:45:58 +01:00
|
|
|
(-> state
|
|
|
|
(cancel-timer)
|
|
|
|
(dissoc :over :subscr)))))
|
2020-06-19 12:06:22 +02:00
|
|
|
|
|
|
|
subscribe-to-drag-end
|
|
|
|
(fn []
|
|
|
|
(when (nil? (:subscr @state))
|
|
|
|
(swap! state
|
|
|
|
#(assoc % :subscr (rx/sub! global-drag-end cleanup)))))
|
2020-04-10 14:30:06 +02:00
|
|
|
|
|
|
|
on-drag-start
|
|
|
|
(fn [event]
|
2021-03-29 12:56:51 +02:00
|
|
|
(if disabled
|
|
|
|
(dom/prevent-default event)
|
2021-06-17 16:29:52 +02:00
|
|
|
(do
|
2021-03-29 12:56:51 +02:00
|
|
|
(dom/stop-propagation event)
|
|
|
|
(dnd/set-data! event data-type data)
|
|
|
|
(dnd/set-drag-image! event (invisible-image))
|
|
|
|
(dnd/set-allowed-effect! event "move")
|
|
|
|
(when (fn? on-drag)
|
|
|
|
(on-drag data)))))
|
2020-04-10 14:30:06 +02:00
|
|
|
|
2020-05-25 10:01:57 +02:00
|
|
|
on-drag-enter
|
2020-04-10 14:30:06 +02:00
|
|
|
(fn [event]
|
2020-05-25 10:01:57 +02:00
|
|
|
(dom/prevent-default event) ;; prevent default to allow drag enter
|
2020-06-01 14:51:22 +02:00
|
|
|
(when-not (dnd/from-child? event)
|
|
|
|
(dom/stop-propagation event)
|
2020-06-19 12:06:22 +02:00
|
|
|
(subscribe-to-drag-end)
|
2020-06-01 14:51:22 +02:00
|
|
|
;; (dnd/trace event data "drag-enter")
|
|
|
|
(when (fn? on-hold)
|
|
|
|
(swap! state (fn [state]
|
|
|
|
(-> state
|
|
|
|
(cancel-timer)
|
|
|
|
(set-timer 1000 on-hold)))))))
|
2020-04-10 14:30:06 +02:00
|
|
|
|
2020-05-25 10:01:57 +02:00
|
|
|
on-drag-over
|
|
|
|
(fn [event]
|
2020-06-01 14:51:22 +02:00
|
|
|
(when (dnd/has-type? event data-type)
|
|
|
|
(dom/prevent-default event) ;; prevent default to allow drag over
|
|
|
|
(when-not (dnd/from-child? event)
|
|
|
|
(dom/stop-propagation event)
|
2020-06-19 12:06:22 +02:00
|
|
|
(subscribe-to-drag-end)
|
2020-06-01 14:51:22 +02:00
|
|
|
;; (dnd/trace event data "drag-over")
|
|
|
|
(let [side (dnd/drop-side event detect-center?)]
|
2020-05-25 12:15:36 +02:00
|
|
|
(swap! state assoc :over side)))))
|
2020-04-10 14:30:06 +02:00
|
|
|
|
|
|
|
on-drag-leave
|
|
|
|
(fn [event]
|
2020-06-01 14:51:22 +02:00
|
|
|
(when-not (dnd/from-child? event)
|
|
|
|
;; (dnd/trace event data "drag-leave")
|
2020-06-19 12:06:22 +02:00
|
|
|
(cleanup)))
|
2020-04-10 14:30:06 +02:00
|
|
|
|
|
|
|
on-drop'
|
|
|
|
(fn [event]
|
|
|
|
(dom/stop-propagation event)
|
2020-06-01 14:51:22 +02:00
|
|
|
;; (dnd/trace event data "drop")
|
|
|
|
(let [side (dnd/drop-side event detect-center?)
|
|
|
|
drop-data (dnd/get-data event data-type)]
|
2020-06-19 12:06:22 +02:00
|
|
|
(cleanup)
|
|
|
|
(rx/push! global-drag-end nil)
|
2020-04-10 14:30:06 +02:00
|
|
|
(when (fn? on-drop)
|
2020-06-01 14:51:22 +02:00
|
|
|
(on-drop side drop-data))))
|
2020-04-10 14:30:06 +02:00
|
|
|
|
|
|
|
on-drag-end
|
|
|
|
(fn [event]
|
2020-06-19 12:06:22 +02:00
|
|
|
(dom/stop-propagation event)
|
2020-06-01 14:51:22 +02:00
|
|
|
;; (dnd/trace event data "drag-end")
|
2020-06-19 12:06:22 +02:00
|
|
|
(rx/push! global-drag-end nil)
|
|
|
|
(cleanup))
|
2020-04-10 14:30:06 +02:00
|
|
|
|
|
|
|
on-mount
|
|
|
|
(fn []
|
|
|
|
(let [dom (mf/ref-val ref)]
|
|
|
|
(.setAttribute dom "draggable" true)
|
|
|
|
|
2020-06-19 12:06:22 +02:00
|
|
|
;; Register all events in the (default) bubble mode, so that they
|
|
|
|
;; are captured by the most leaf item. The handler will stop
|
|
|
|
;; propagation, so they will not go up in the containment tree.
|
2020-04-10 14:30:06 +02:00
|
|
|
(.addEventListener dom "dragstart" on-drag-start false)
|
2020-05-25 10:01:57 +02:00
|
|
|
(.addEventListener dom "dragenter" on-drag-enter false)
|
2020-04-10 14:30:06 +02:00
|
|
|
(.addEventListener dom "dragover" on-drag-over false)
|
2020-06-19 12:06:22 +02:00
|
|
|
(.addEventListener dom "dragleave" on-drag-leave false)
|
2020-04-10 14:30:06 +02:00
|
|
|
(.addEventListener dom "drop" on-drop' false)
|
|
|
|
(.addEventListener dom "dragend" on-drag-end false)
|
|
|
|
#(do
|
|
|
|
(.removeEventListener dom "dragstart" on-drag-start)
|
2020-05-25 10:01:57 +02:00
|
|
|
(.removeEventListener dom "dragenter" on-drag-enter)
|
2020-04-10 14:30:06 +02:00
|
|
|
(.removeEventListener dom "dragover" on-drag-over)
|
|
|
|
(.removeEventListener dom "dragleave" on-drag-leave)
|
|
|
|
(.removeEventListener dom "drop" on-drop')
|
|
|
|
(.removeEventListener dom "dragend" on-drag-end))))]
|
|
|
|
|
|
|
|
(mf/use-effect
|
2020-05-25 12:15:36 +02:00
|
|
|
(mf/deps data on-drop)
|
2020-04-10 14:30:06 +02:00
|
|
|
on-mount)
|
2020-05-25 10:01:57 +02:00
|
|
|
|
2020-04-10 14:30:06 +02:00
|
|
|
[(deref state) ref]))
|
2020-05-25 10:01:57 +02:00
|
|
|
|
2020-06-02 15:49:18 +02:00
|
|
|
|
|
|
|
(defn use-stream
|
2021-11-15 09:51:34 -05:00
|
|
|
"Wraps the subscription to a stream into a `use-effect` call"
|
2021-03-18 21:58:29 +01:00
|
|
|
([stream on-subscribe]
|
|
|
|
(use-stream stream (mf/deps) on-subscribe))
|
|
|
|
([stream deps on-subscribe]
|
|
|
|
(mf/use-effect
|
|
|
|
deps
|
|
|
|
(fn []
|
|
|
|
(let [sub (->> stream (rx/subs on-subscribe))]
|
|
|
|
#(rx/dispose! sub))))))
|
2020-12-04 13:25:41 +01:00
|
|
|
|
|
|
|
;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
|
2021-03-16 11:39:09 +01:00
|
|
|
(defn use-previous
|
|
|
|
[value]
|
|
|
|
(let [ref (mf/use-ref value)]
|
2020-12-04 13:25:41 +01:00
|
|
|
(mf/use-effect
|
2021-03-16 11:39:09 +01:00
|
|
|
(mf/deps value)
|
|
|
|
(fn []
|
|
|
|
(mf/set-ref-val! ref value)))
|
2020-12-04 13:25:41 +01:00
|
|
|
(mf/ref-val ref)))
|
2021-05-21 14:51:02 +02:00
|
|
|
|
|
|
|
(defn use-equal-memo
|
|
|
|
[val]
|
|
|
|
(let [ref (mf/use-ref nil)]
|
|
|
|
(when-not (= (mf/ref-val ref) val)
|
|
|
|
(mf/set-ref-val! ref val))
|
|
|
|
(mf/ref-val ref)))
|
|
|
|
|
|
|
|
(defn- ssr?
|
|
|
|
"Checks if the current environment is run under a SSR context"
|
|
|
|
[]
|
|
|
|
(try
|
|
|
|
(not js/window)
|
2021-06-17 16:29:52 +02:00
|
|
|
(catch :default _e
|
2021-05-21 14:51:02 +02:00
|
|
|
;; When exception accessing window we're in ssr
|
|
|
|
true)))
|
|
|
|
|
|
|
|
(defn use-effect-ssr
|
|
|
|
"Use effect that handles SSR"
|
|
|
|
[deps effect-fn]
|
|
|
|
|
|
|
|
(if (ssr?)
|
|
|
|
(let [ret (effect-fn)]
|
|
|
|
(when (fn? ret) (ret)))
|
|
|
|
(mf/use-effect deps effect-fn)))
|
2022-02-23 16:30:01 +01:00
|
|
|
|
|
|
|
(defn with-focus-objects
|
|
|
|
([objects]
|
|
|
|
(let [focus (mf/deref refs/workspace-focus-selected)]
|
|
|
|
(with-focus-objects objects focus)))
|
|
|
|
|
|
|
|
([objects focus]
|
|
|
|
(let [objects (mf/use-memo
|
|
|
|
(mf/deps focus objects)
|
|
|
|
#(cp/focus-objects objects focus))]
|
|
|
|
objects)))
|