react.cljs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. (ns frontend.db.react
  2. "Transact the tx with some specified relationship so that the components will
  3. be refreshed when subscribed data changed.
  4. It'll be great if we can find an automatically resolving and performant
  5. solution.
  6. "
  7. (:require [clojure.core.async :as async]
  8. [datascript.core :as d]
  9. [frontend.date :as date]
  10. [frontend.db.async.util :as db-async-util]
  11. [frontend.db.conn :as conn]
  12. [frontend.db.utils :as db-utils]
  13. [frontend.state :as state]
  14. [frontend.util :as util]
  15. [logseq.common.util :as common-util]
  16. [logseq.db :as ldb]
  17. [promesa.core :as p]))
  18. ;; Query atom of map of Key ([repo q inputs]) -> atom
  19. ;; TODO: replace with LRUCache, only keep the latest 20 or 50 items?
  20. (defonce query-state (atom {}))
  21. ;; Current dynamic component
  22. (def ^:dynamic *query-component* nil)
  23. ;; Which reactive queries are triggered by the current component
  24. (def ^:dynamic *reactive-queries* nil)
  25. ;; component -> query-key
  26. (defonce component->query-key (volatile! {}))
  27. ;; query-key -> component-set
  28. (defonce query-key->components (volatile! {}))
  29. (defn set-new-result!
  30. [k new-result]
  31. (when-let [result-atom (get-in @query-state [k :result])]
  32. (reset! result-atom new-result)))
  33. (defn clear-query-state!
  34. []
  35. (reset! query-state {}))
  36. (defn add-q!
  37. [k query inputs result-atom transform-fn query-fn inputs-fn]
  38. (swap! query-state assoc k {:query query
  39. :inputs inputs
  40. :result result-atom
  41. :transform-fn transform-fn
  42. :query-fn query-fn
  43. :inputs-fn inputs-fn})
  44. result-atom)
  45. (defn remove-q!
  46. [k]
  47. (swap! query-state dissoc k))
  48. (defn add-query-component!
  49. [k component]
  50. (when (and k component)
  51. (vswap! component->query-key update component (fnil conj #{}) k)
  52. (vswap! query-key->components update k (fnil conj #{}) component)))
  53. (defn remove-query-component!
  54. [component]
  55. (when-let [queries (get @component->query-key component)]
  56. (doseq [query queries]
  57. (vswap! query-key->components
  58. (fn [m]
  59. (if-let [components* (not-empty (disj (get m query) component))]
  60. (assoc m query components*)
  61. (dissoc m query))))
  62. (when (empty? (get @query-key->components query))
  63. (remove-q! query))))
  64. (vswap! component->query-key dissoc component))
  65. ;; Reactive query
  66. (defn get-query-cached-result
  67. [k]
  68. (when-let [result (get @query-state k)]
  69. (when (satisfies? IWithMeta @(:result result))
  70. (set! (.-state (:result result))
  71. @(:result result)))
  72. (:result result)))
  73. (defn- <q-aux
  74. [repo db query-fn inputs-fn k query inputs built-in-query?]
  75. (let [kv? (and (vector? k) (= :kv (second k)))
  76. q (if util/node-test?
  77. (fn [query inputs] (apply d/q query db inputs))
  78. (fn [query inputs]
  79. (let [q-f #(apply db-async-util/<q repo {} (cons query inputs))]
  80. (if built-in-query?
  81. ;; delay built-in-queries to not block journal rendering
  82. (p/let [_ (p/delay 100)]
  83. (q-f))
  84. (q-f)))))]
  85. (when (or query-fn query kv?)
  86. (cond
  87. query-fn
  88. (query-fn db nil)
  89. kv?
  90. (db-utils/entity db (last k))
  91. inputs-fn
  92. (let [inputs (inputs-fn)]
  93. (q query inputs))
  94. (seq inputs)
  95. (q query inputs)
  96. :else
  97. (q query nil)))))
  98. (defn q
  99. [repo k {:keys [use-cache? transform-fn query-fn inputs-fn
  100. disable-reactive? return-promise? built-in-query?]
  101. :or {use-cache? true
  102. transform-fn identity}} query & inputs]
  103. ;; {:pre [(s/valid? :frontend.worker.react/block k)]}
  104. (let [origin-key k
  105. k (vec (cons repo k))]
  106. (when-let [db (conn/get-db repo)]
  107. (let [result-atom (get-query-cached-result k)]
  108. (when-let [component *query-component*]
  109. (add-query-component! k component))
  110. (when-let [queries *reactive-queries*]
  111. (swap! queries conj origin-key))
  112. (if (and use-cache? result-atom)
  113. result-atom
  114. (let [result-atom (or result-atom (atom nil))
  115. p-or-value (<q-aux repo db query-fn inputs-fn k query inputs built-in-query?)]
  116. (when-not disable-reactive?
  117. (add-q! k query inputs result-atom transform-fn query-fn inputs-fn))
  118. (cond
  119. return-promise?
  120. p-or-value
  121. (p/promise? p-or-value)
  122. (do
  123. (p/let [result p-or-value
  124. result' (transform-fn result)]
  125. (reset! result-atom result'))
  126. result-atom)
  127. :else
  128. (let [result' (transform-fn p-or-value)]
  129. ;; Don't notify watches now
  130. (set! (.-state result-atom) result')
  131. result-atom))))))))
  132. (defn get-current-page
  133. []
  134. (let [match (:route-match @state/state)
  135. route-name (get-in match [:data :name])
  136. page (case route-name
  137. :page
  138. (get-in match [:path-params :name])
  139. (date/journal-name))]
  140. (when page
  141. (if (common-util/uuid-string? page)
  142. (db-utils/entity [:block/uuid (uuid page)])
  143. (ldb/get-page (conn/get-db) page)))))
  144. (defn- execute-query!
  145. [graph db k {:keys [query inputs transform-fn query-fn inputs-fn result built-in-query?]
  146. :or {transform-fn identity}}]
  147. (p/let [p-or-value (<q-aux graph db query-fn inputs-fn k query inputs built-in-query?)
  148. result' (transform-fn p-or-value)]
  149. (when-not (= result' result)
  150. (set-new-result! k result'))))
  151. (defn refresh-affected-queries!
  152. [repo-url affected-keys]
  153. (util/profile
  154. "refresh!"
  155. (let [db (conn/get-db repo-url)
  156. affected-keys-set (set affected-keys)
  157. state (->> (keep (fn [[k cache]]
  158. (let [k' (vec (rest k))]
  159. (when (and (= (first k) repo-url)
  160. (or (contains? affected-keys-set k')
  161. (contains? #{:custom :kv} (first k'))))
  162. [k' cache]))) @query-state)
  163. (into {}))
  164. all-keys (concat (distinct affected-keys)
  165. (filter #(contains? #{:custom :kv} (first %)) (keys state)))]
  166. (doseq [k all-keys]
  167. (when-let [cache (get state k)]
  168. (let [{:keys [query query-fn]} cache
  169. custom? (= :custom (first k))]
  170. (when (or query query-fn)
  171. (try
  172. (let [f #(execute-query! repo-url db (vec (cons repo-url k)) cache)]
  173. (if custom?
  174. (async/put! (state/get-reactive-custom-queries-chan) [f query])
  175. (f)))
  176. (catch :default e
  177. (js/console.error e)
  178. nil)))))))))
  179. (defn refresh!
  180. "Re-compute corresponding queries (from tx) and refresh the related react components."
  181. [repo-url affected-keys]
  182. (when (and repo-url (seq affected-keys))
  183. (refresh-affected-queries! repo-url affected-keys)))
  184. (defn run-custom-queries-when-idle!
  185. []
  186. (let [chan (state/get-reactive-custom-queries-chan)]
  187. (async/go-loop []
  188. (let [[f query] (async/<! chan)]
  189. (try
  190. (if (state/input-idle? (state/get-current-repo))
  191. (f)
  192. (do
  193. (async/<! (async/timeout 2000))
  194. (async/put! chan [f query])))
  195. (catch :default error
  196. (let [type :custom-query/failed]
  197. (js/console.error (str type "\n" query))
  198. (js/console.error error)))))
  199. (recur))
  200. chan))