core.cljs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. (ns frontend.modules.shortcut.core
  2. (:require [clojure.string :as str]
  3. [frontend.handler.config :as config-handler]
  4. [frontend.handler.notification :as notification]
  5. [frontend.modules.shortcut.data-helper :as dh]
  6. [frontend.modules.shortcut.config :as shortcut-config]
  7. [frontend.state :as state]
  8. [frontend.util :as util]
  9. [goog.events :as events]
  10. [goog.ui.KeyboardShortcutHandler.EventType :as EventType]
  11. [lambdaisland.glogi :as log]
  12. [goog.functions :refer [debounce]])
  13. (:import [goog.events KeyCodes KeyHandler KeyNames]
  14. [goog.ui KeyboardShortcutHandler]))
  15. (def *installed (atom {}))
  16. (def *inited? (atom false))
  17. (def *pending (atom []))
  18. (def global-keys #js
  19. [KeyCodes/TAB
  20. KeyCodes/ENTER
  21. KeyCodes/BACKSPACE KeyCodes/DELETE
  22. KeyCodes/UP KeyCodes/LEFT KeyCodes/DOWN KeyCodes/RIGHT])
  23. (def key-names (js->clj KeyNames))
  24. (declare register-shortcut!)
  25. (defn consume-pending-shortcuts!
  26. []
  27. (when (and @*inited? (seq @*pending))
  28. (doseq [[handler-id id shortcut] @*pending]
  29. (register-shortcut! handler-id id shortcut))
  30. (reset! *pending [])))
  31. (defn- get-handler-by-id
  32. [handler-id]
  33. (-> (filter #(= (:group %) handler-id) (vals @*installed))
  34. first
  35. :handler))
  36. (defn register-shortcut!
  37. "Register a shortcut, notice the id need to be a namespaced keyword to avoid
  38. conflicts.
  39. Example:
  40. (register-shortcut! :shortcut.handler/misc :foo/bar {:binding \"mod+shift+8\"
  41. :fn (fn [_state _event]
  42. (js/alert \"test shortcut\"))})"
  43. ([handler-id id]
  44. (register-shortcut! handler-id id nil))
  45. ([handler-id id shortcut-map]
  46. (if (and (keyword? handler-id) (not @*inited?))
  47. (swap! *pending conj [handler-id id shortcut-map])
  48. (when-let [handler (if (or (string? handler-id) (keyword? handler-id))
  49. (let [handler-id (keyword handler-id)]
  50. (get-handler-by-id handler-id))
  51. ;; handler
  52. handler-id)]
  53. (when shortcut-map
  54. (shortcut-config/add-shortcut! handler-id id shortcut-map))
  55. (when-not (false? (dh/shortcut-binding id))
  56. (doseq [k (dh/shortcut-binding id)]
  57. (try
  58. (log/debug :shortcut/register-shortcut {:id id :binding k})
  59. (.registerShortcut handler (util/keyname id) (dh/normalize-user-keyname k))
  60. (catch :default e
  61. (log/error :shortcut/register-shortcut {:id id
  62. :binding k
  63. :error e})
  64. (notification/show! (str/join " " [id k (.-message e)]) :error false)))))))))
  65. (defn unregister-shortcut!
  66. "Unregister a shortcut.
  67. Example:
  68. (unregister-shortcut! :shortcut.handler/misc :foo/bar)"
  69. [handler-id shortcut-id]
  70. (when-let [handler (get-handler-by-id handler-id)]
  71. (when-let [ks (dh/shortcut-binding shortcut-id)]
  72. (doseq [k ks]
  73. (.unregisterShortcut ^js handler (dh/normalize-user-keyname k))))
  74. (shortcut-config/remove-shortcut! handler-id shortcut-id)))
  75. (defn uninstall-shortcut!
  76. [install-id]
  77. (when-let [handler (-> (get @*installed install-id)
  78. :handler)]
  79. (.dispose ^js handler)
  80. (swap! *installed dissoc install-id)))
  81. (defn install-shortcut!
  82. [handler-id {:keys [set-global-keys?
  83. prevent-default?
  84. state]
  85. :or {set-global-keys? true
  86. prevent-default? false}}]
  87. (when-let [install-id (get-handler-by-id handler-id)]
  88. (uninstall-shortcut! install-id))
  89. (let [shortcut-map (dh/shortcut-map handler-id state)
  90. handler (new KeyboardShortcutHandler js/window)]
  91. ;; set arrows enter, tab to global
  92. (when set-global-keys?
  93. (.setGlobalKeys handler global-keys))
  94. (.setAlwaysPreventDefault handler prevent-default?)
  95. ;; register shortcuts
  96. (doseq [[id _] shortcut-map]
  97. ;; (log/info :shortcut/install-shortcut {:id id :shortcut (str (dh/shortcut-binding id))})
  98. (register-shortcut! handler id))
  99. (let [f (fn [e]
  100. (let [shortcut-map (dh/shortcut-map handler-id state)
  101. dispatch-fn (get shortcut-map (keyword (.-identifier e)))]
  102. ;; trigger fn
  103. (when dispatch-fn (dispatch-fn e))))
  104. install-id (random-uuid)
  105. data {install-id
  106. {:group handler-id
  107. :dispatch-fn f
  108. :handler handler}}]
  109. (.listen handler EventType/SHORTCUT_TRIGGERED f)
  110. (swap! *installed merge data)
  111. install-id)))
  112. (defn- install-shortcuts!
  113. []
  114. (->> [:shortcut.handler/misc
  115. :shortcut.handler/editor-global
  116. :shortcut.handler/global-non-editing-only
  117. :shortcut.handler/global-prevent-default]
  118. (map #(install-shortcut! % {}))
  119. doall))
  120. (defn mixin [handler-id]
  121. {:did-mount
  122. (fn [state]
  123. (let [install-id (install-shortcut! handler-id {:state state})]
  124. (assoc state ::install-id install-id)))
  125. :did-remount (fn [old-state new-state]
  126. (uninstall-shortcut! (::install-id old-state))
  127. (when-let [install-id (install-shortcut! handler-id {:state new-state})]
  128. (assoc new-state ::install-id install-id)))
  129. :will-unmount
  130. (fn [state]
  131. (when-let [install-id (::install-id state)]
  132. (uninstall-shortcut! install-id))
  133. state)})
  134. (defn unlisten-all []
  135. (doseq [{:keys [handler group]} (vals @*installed)
  136. :when (not= group :shortcut.handler/misc)]
  137. (.removeAllListeners handler)))
  138. (defn listen-all []
  139. (doseq [{:keys [handler group dispatch-fn]} (vals @*installed)
  140. :when (not= group :shortcut.handler/misc)]
  141. (events/listen handler EventType/SHORTCUT_TRIGGERED dispatch-fn)))
  142. (def disable-all-shortcuts
  143. {:will-mount
  144. (fn [state]
  145. (unlisten-all)
  146. state)
  147. :will-unmount
  148. (fn [state]
  149. (listen-all)
  150. state)})
  151. (defn refresh-internal!
  152. "Always use this function to refresh shortcuts"
  153. []
  154. (when-not (:ui/shortcut-handler-refreshing? @state/state)
  155. (state/set-state! :ui/shortcut-handler-refreshing? true)
  156. (doseq [id (keys @*installed)]
  157. (uninstall-shortcut! id))
  158. (install-shortcuts!)
  159. (state/pub-event! [:shortcut-handler-refreshed])
  160. (state/set-state! :ui/shortcut-handler-refreshing? false)))
  161. (def refresh! (debounce refresh-internal! 1000))
  162. (defn- name-with-meta [e]
  163. (let [ctrl (.-ctrlKey e)
  164. alt (.-altKey e)
  165. meta (.-metaKey e)
  166. shift (.-shiftKey e)
  167. keyname (get key-names (str (.-keyCode e)))]
  168. (cond->> keyname
  169. ctrl (str "ctrl+")
  170. alt (str "alt+")
  171. meta (str "meta+")
  172. shift (str "shift+"))))
  173. (defn- keyname [e]
  174. (let [name (get key-names (str (.-keyCode e)))]
  175. (case name
  176. nil nil
  177. ("ctrl" "shift" "alt" "esc") nil
  178. (str " " (name-with-meta e)))))
  179. (defn record! []
  180. {:did-mount
  181. (fn [state]
  182. (let [handler (KeyHandler. js/document)
  183. keystroke (:rum/local state)]
  184. (doseq [id (keys @*installed)]
  185. (uninstall-shortcut! id))
  186. (events/listen handler "key"
  187. (fn [e]
  188. (.preventDefault e)
  189. (swap! keystroke #(str % (keyname e)))))
  190. (assoc state ::key-record-handler handler)))
  191. :will-unmount
  192. (fn [{:rum/keys [args local] :as state}]
  193. (let [k (first args)
  194. keystroke (str/trim @local)]
  195. (when-not (empty? keystroke)
  196. (config-handler/set-config! :shortcuts (merge
  197. (:shortcuts (state/get-config))
  198. {k keystroke}))))
  199. (when-let [^js handler (::key-record-handler state)]
  200. (.dispose handler))
  201. (js/setTimeout #(refresh!) 500)
  202. (dissoc state ::key-record-handler))})