mixins.cljs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. (ns frontend.mixins
  2. "Rum mixins for use in components"
  3. (:require [frontend.state :as state]
  4. [frontend.util :refer [profile] :as util]
  5. [goog.dom :as dom]
  6. [rum.core :as rum])
  7. (:import [goog.events EventHandler]))
  8. (defn detach
  9. "Detach all event listeners."
  10. [state]
  11. (some-> state ::event-handler .removeAll))
  12. (defn listen
  13. "Register an event `handler` for events of `type` on `target`."
  14. [state target type handler & [opts]]
  15. (when-let [^EventHandler event-handler (::event-handler state)]
  16. (.listen event-handler target (name type) handler (clj->js opts))))
  17. (def event-handler-mixin
  18. "The event handler mixin."
  19. {:will-mount
  20. (fn [state]
  21. (assoc state ::event-handler (EventHandler.)))
  22. :will-unmount
  23. (fn [state]
  24. (detach state)
  25. (dissoc state ::event-handler))})
  26. (defn hide-when-esc-or-outside
  27. [state & {:keys [on-hide node visibilitychange? outside?]}]
  28. (let [opts (last (:rum/args state))
  29. outside? (cond-> opts (nil? outside?) (:outside?))]
  30. (try
  31. (let [dom-node (rum/dom-node state)]
  32. (when-let [dom-node (or node dom-node)]
  33. (let [click-fn (fn [e]
  34. (let [target (.. e -target)]
  35. ;; If the click target is outside of current node
  36. (when (and
  37. (not (dom/contains dom-node target))
  38. (not (.contains (.-classList target) "ignore-outside-event")))
  39. (on-hide state e :click))))]
  40. (when-not (false? outside?)
  41. (listen state js/window "mousedown" click-fn)))
  42. (listen state js/window "keydown"
  43. (fn [e]
  44. (case (.-keyCode e)
  45. ;; Esc
  46. 27 (on-hide state e :esc)
  47. nil)))
  48. (when visibilitychange?
  49. (listen state js/window "visibilitychange"
  50. (fn [e]
  51. (on-hide state e :visibilitychange))))))
  52. (catch :default _e
  53. ;; TODO: Unable to find node on an unmounted component.
  54. nil))))
  55. (defn on-enter
  56. [state & {on-enter-fn :on-enter :keys [node]}]
  57. (let [node (or node (rum/dom-node state))]
  58. (listen state node "keyup"
  59. (fn [e]
  60. (case (.-keyCode e)
  61. ;; Enter
  62. 13 (on-enter-fn e)
  63. nil)))))
  64. (defn on-key-up
  65. "Caution: This mixin uses a different args than on-key-down"
  66. [state keycode-map all-handler]
  67. (listen state js/window "keyup"
  68. (fn [e]
  69. (let [key-code (.-keyCode e)]
  70. (when-let [f (get keycode-map key-code)]
  71. (f state e))
  72. (when all-handler (all-handler e key-code))))))
  73. (defn on-key-down
  74. ([state keycode-map]
  75. (on-key-down state keycode-map {}))
  76. ([state keycode-map {:keys [not-matched-handler all-handler target keycode?]
  77. :or {keycode? true}}]
  78. (listen state (or target js/window) "keydown"
  79. (fn [e]
  80. (let [key (if keycode? (.-keyCode e) (.-key e))]
  81. (if-let [f (get keycode-map key)]
  82. (f state e)
  83. (when (and not-matched-handler (fn? not-matched-handler))
  84. (not-matched-handler e key)))
  85. (when (and all-handler (fn? all-handler))
  86. (all-handler e key)))))))
  87. (defn event-mixin
  88. ([attach-listeners]
  89. (event-mixin attach-listeners identity))
  90. ([attach-listeners init-callback]
  91. (merge
  92. event-handler-mixin
  93. {:init (fn [state _props]
  94. (init-callback state))
  95. :did-mount (fn [state]
  96. (attach-listeners state)
  97. state)})))
  98. (defn modal
  99. [k]
  100. (event-mixin
  101. (fn [state]
  102. (let [open? (get state k)]
  103. (hide-when-esc-or-outside
  104. state
  105. :on-hide (fn []
  106. (when (and open? @open?)
  107. (reset! open? false))))))
  108. (fn [state]
  109. (let [open? (atom false)
  110. component (:rum/react-component state)]
  111. (add-watch open? ::open
  112. (fn [_ _ _ _]
  113. (rum/request-render component)))
  114. (assoc state
  115. :open? open?
  116. :close-fn (fn []
  117. (reset! open? false))
  118. :open-fn (fn []
  119. (reset! open? true))
  120. :toggle-fn (fn []
  121. (swap! open? not)))))))
  122. (def container-id
  123. "Notice: the first parameter needs to be a `config` with `id`, optional `sidebar?`, `whiteboard?`"
  124. {:init (fn [state]
  125. (let [config (first (:rum/args state))
  126. key (select-keys config [:id :sidebar? :whiteboard? :embed? :custom-query? :query :current-block :table?])
  127. container-id (or (:container-id config) (state/get-container-id key))]
  128. (assoc state :container-id container-id)))})
  129. (defn perf-measure-mixin
  130. "Does performance measurements in development."
  131. [desc]
  132. {:wrap-render
  133. (fn wrap-render [render-fn]
  134. (fn [state]
  135. (profile
  136. (str "Render " desc)
  137. (render-fn state))))})