mirror of
https://github.com/logseq/logseq.git
synced 2026-05-02 18:06:32 +00:00
158 lines
5.4 KiB
Clojure
158 lines
5.4 KiB
Clojure
(ns frontend.mixins
|
|
"Rum mixins for use in components"
|
|
(:require [rum.core :as rum]
|
|
[goog.dom :as dom]
|
|
[frontend.util :refer [profile] :as util]
|
|
[frontend.state :as state]
|
|
[goog.functions :as gfun])
|
|
(:import [goog.events EventHandler]))
|
|
|
|
(defn detach
|
|
"Detach all event listeners."
|
|
[state]
|
|
(some-> state ::event-handler .removeAll))
|
|
|
|
(defn listen
|
|
"Register an event `handler` for events of `type` on `target`."
|
|
[state target type handler & [opts]]
|
|
(when-let [^EventHandler event-handler (::event-handler state)]
|
|
(.listen event-handler target (name type) handler (clj->js opts))))
|
|
|
|
(def event-handler-mixin
|
|
"The event handler mixin."
|
|
{:will-mount
|
|
(fn [state]
|
|
(assoc state ::event-handler (EventHandler.)))
|
|
:will-unmount
|
|
(fn [state]
|
|
(detach state)
|
|
(dissoc state ::event-handler))})
|
|
|
|
(defn hide-when-esc-or-outside
|
|
[state & {:keys [on-hide node visibilitychange? outside?]}]
|
|
(let [opts (last (:rum/args state))
|
|
outside? (cond-> opts (nil? outside?) (:outside?))]
|
|
(try
|
|
(let [dom-node (rum/dom-node state)]
|
|
(when-let [dom-node (or node dom-node)]
|
|
(let [click-fn (fn [e]
|
|
(let [target (.. e -target)]
|
|
;; If the click target is outside of current node
|
|
(when (and
|
|
(not (dom/contains dom-node target))
|
|
(not (.contains (.-classList target) "ignore-outside-event")))
|
|
(on-hide state e :click))))]
|
|
(when-not (false? outside?)
|
|
(listen state js/window "mousedown" click-fn)))
|
|
(listen state js/window "keydown"
|
|
(fn [e]
|
|
(case (.-keyCode e)
|
|
;; Esc
|
|
27 (on-hide state e :esc)
|
|
nil)))
|
|
(when visibilitychange?
|
|
(listen state js/window "visibilitychange"
|
|
(fn [e]
|
|
(on-hide state e :visibilitychange))))))
|
|
(catch :default _e
|
|
;; TODO: Unable to find node on an unmounted component.
|
|
nil))))
|
|
|
|
(defn on-enter
|
|
[state & {on-enter-fn :on-enter :keys [node]}]
|
|
(let [node (or node (rum/dom-node state))]
|
|
(listen state node "keyup"
|
|
(fn [e]
|
|
(case (.-keyCode e)
|
|
;; Enter
|
|
13 (on-enter-fn e)
|
|
nil)))))
|
|
|
|
(defn on-key-up
|
|
"Caution: This mixin uses a different args than on-key-down"
|
|
[state keycode-map all-handler]
|
|
(listen state js/window "keyup"
|
|
(fn [e]
|
|
(let [key-code (.-keyCode e)]
|
|
(when-let [f (get keycode-map key-code)]
|
|
(f state e))
|
|
(when all-handler (all-handler e key-code))))))
|
|
|
|
(defn on-key-down
|
|
([state keycode-map]
|
|
(on-key-down state keycode-map {}))
|
|
([state keycode-map {:keys [not-matched-handler all-handler target keycode?]
|
|
:or {keycode? true}}]
|
|
(listen state (or target js/window) "keydown"
|
|
(fn [e]
|
|
(let [key (if keycode? (.-keyCode e) (.-key e))]
|
|
(if-let [f (get keycode-map key)]
|
|
(f state e)
|
|
(when (and not-matched-handler (fn? not-matched-handler))
|
|
(not-matched-handler e key)))
|
|
(when (and all-handler (fn? all-handler))
|
|
(all-handler e key)))))))
|
|
|
|
(defn event-mixin
|
|
([attach-listeners]
|
|
(event-mixin attach-listeners identity))
|
|
([attach-listeners init-callback]
|
|
(merge
|
|
event-handler-mixin
|
|
{:init (fn [state _props]
|
|
(init-callback state))
|
|
:did-mount (fn [state]
|
|
(attach-listeners state)
|
|
state)})))
|
|
|
|
(defn modal
|
|
[k]
|
|
(event-mixin
|
|
(fn [state]
|
|
(let [open? (get state k)]
|
|
(hide-when-esc-or-outside
|
|
state
|
|
:on-hide (fn []
|
|
(when (and open? @open?)
|
|
(reset! open? false))))))
|
|
(fn [state]
|
|
(let [open? (atom false)
|
|
component (:rum/react-component state)]
|
|
(add-watch open? ::open
|
|
(fn [_ _ _ _]
|
|
(rum/request-render component)))
|
|
(assoc state
|
|
:open? open?
|
|
:close-fn (fn []
|
|
(reset! open? false))
|
|
:open-fn (fn []
|
|
(reset! open? true))
|
|
:toggle-fn (fn []
|
|
(swap! open? not)))))))
|
|
|
|
(def container-id
|
|
"Notice: the first parameter needs to be a `config` with `id`, optional `sidebar?`, `whiteboard?`"
|
|
{:init (fn [state]
|
|
(let [config (first (:rum/args state))
|
|
key (select-keys config [:id :sidebar? :whiteboard? :embed? :custom-query? :query :current-block :table?])
|
|
container-id (or (:container-id config) (state/get-container-id key))]
|
|
(assoc state :container-id container-id)))})
|
|
|
|
(defn perf-measure-mixin
|
|
"Does performance measurements in development."
|
|
[desc]
|
|
{:wrap-render
|
|
(fn wrap-render [render-fn]
|
|
(fn [state]
|
|
(profile
|
|
(str "Render " desc)
|
|
(render-fn state))))})
|
|
|
|
(defn use-debounce
|
|
"A rumext custom hook that debounces the value changes"
|
|
[ms value]
|
|
(let [[state update-fn] (rum/use-state value)
|
|
update-fn (rum/use-callback (gfun/debounce update-fn ms) [])]
|
|
(rum/use-effect! #(update-fn value) #js [value])
|
|
state))
|