react.cljs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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 async-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. :async-query-fn async-query-fn
  44. :inputs-fn inputs-fn})
  45. result-atom)
  46. (defn remove-q!
  47. [k]
  48. (swap! query-state dissoc k))
  49. (defn add-query-component!
  50. [k component]
  51. (when (and k component)
  52. (vswap! component->query-key update component (fnil conj #{}) k)
  53. (vswap! query-key->components update k (fnil conj #{}) component)))
  54. (defn remove-query-component!
  55. [component]
  56. (when-let [queries (get @component->query-key component)]
  57. (doseq [query queries]
  58. (vswap! query-key->components
  59. (fn [m]
  60. (if-let [components* (not-empty (disj (get m query) component))]
  61. (assoc m query components*)
  62. (dissoc m query))))
  63. (when (empty? (get @query-key->components query))
  64. (remove-q! query))))
  65. (vswap! component->query-key dissoc component))
  66. ;; Reactive query
  67. (defn get-query-cached-result
  68. [k]
  69. (when-let [result (get @query-state k)]
  70. (when (satisfies? IWithMeta @(:result result))
  71. (set! (.-state (:result result))
  72. @(:result result)))
  73. (:result result)))
  74. (defn- <q-aux
  75. [repo db query-fn async-query-fn inputs-fn k query inputs]
  76. (let [kv? (and (vector? k) (= :kv (second k)))
  77. q (if util/node-test?
  78. (fn [query inputs] (apply d/q query db inputs))
  79. (fn [query inputs] (apply db-async-util/<q repo {} (cons query inputs))))]
  80. (when (or query-fn async-query-fn query kv?)
  81. (cond
  82. async-query-fn
  83. (async-query-fn)
  84. query-fn
  85. (query-fn db nil)
  86. kv?
  87. (db-utils/entity db (last k))
  88. inputs-fn
  89. (let [inputs (inputs-fn)]
  90. (q query inputs))
  91. (seq inputs)
  92. (q query inputs)
  93. :else
  94. (q query nil)))))
  95. (defn q
  96. [repo k {:keys [use-cache? transform-fn query-fn async-query-fn inputs-fn disable-reactive? return-promise?]
  97. :or {use-cache? true
  98. transform-fn identity}} query & inputs]
  99. ;; {:pre [(s/valid? :frontend.worker.react/block k)]}
  100. (let [origin-key k
  101. k (vec (cons repo k))]
  102. (when-let [db (conn/get-db repo)]
  103. (let [result-atom (get-query-cached-result k)]
  104. (when-let [component *query-component*]
  105. (add-query-component! k component))
  106. (when-let [queries *reactive-queries*]
  107. (swap! queries conj origin-key))
  108. (if (and use-cache? result-atom)
  109. result-atom
  110. (let [result-atom (or result-atom (atom nil))
  111. p-or-value (<q-aux repo db query-fn async-query-fn inputs-fn k query inputs)]
  112. (when-not disable-reactive?
  113. (add-q! k query inputs result-atom transform-fn query-fn async-query-fn inputs-fn))
  114. (cond
  115. return-promise?
  116. p-or-value
  117. (p/promise? p-or-value)
  118. (do
  119. (p/let [result p-or-value
  120. result' (transform-fn result)]
  121. (reset! result-atom result'))
  122. result-atom)
  123. :else
  124. (let [result' (transform-fn p-or-value)]
  125. ;; Don't notify watches now
  126. (set! (.-state result-atom) result')
  127. result-atom))))))))
  128. (defn get-current-page
  129. []
  130. (let [match (:route-match @state/state)
  131. route-name (get-in match [:data :name])
  132. page (case route-name
  133. :page
  134. (get-in match [:path-params :name])
  135. (date/journal-name))]
  136. (when page
  137. (if (common-util/uuid-string? page)
  138. (db-utils/entity [:block/uuid (uuid page)])
  139. (ldb/get-page (conn/get-db) page)))))
  140. (defn- execute-query!
  141. [graph db k {:keys [query inputs transform-fn query-fn async-query-fn inputs-fn result]
  142. :or {transform-fn identity}}]
  143. (p/let [p-or-value (<q-aux graph db query-fn async-query-fn inputs-fn k query inputs)
  144. result' (transform-fn p-or-value)]
  145. (when-not (= result' result)
  146. (set-new-result! k result'))))
  147. (defn refresh-affected-queries!
  148. [repo-url affected-keys]
  149. (util/profile
  150. "refresh!"
  151. (let [db (conn/get-db repo-url)
  152. affected-keys-set (set affected-keys)
  153. state (->> (keep (fn [[k cache]]
  154. (let [k' (vec (rest k))]
  155. (when (and (= (first k) repo-url)
  156. (or (contains? affected-keys-set k')
  157. (contains? #{:custom :kv} (first k'))))
  158. [k' cache]))) @query-state)
  159. (into {}))
  160. all-keys (concat (distinct affected-keys)
  161. (filter #(contains? #{:custom :kv} (first %)) (keys state)))]
  162. (doseq [k all-keys]
  163. (when-let [cache (get state k)]
  164. (let [{:keys [query query-fn]} cache
  165. custom? (= :custom (first k))]
  166. (when (or query query-fn)
  167. (try
  168. (let [f #(execute-query! repo-url db (vec (cons repo-url k)) cache)]
  169. (when-not custom?
  170. (f)))
  171. (catch :default e
  172. (js/console.error e)
  173. nil)))))))))
  174. (defn refresh!
  175. "Re-compute corresponding queries (from tx) and refresh the related react components."
  176. [repo-url affected-keys]
  177. (when (and repo-url (seq affected-keys))
  178. (refresh-affected-queries! repo-url affected-keys)))
  179. (defn run-custom-queries-when-idle!
  180. []
  181. (let [chan (state/get-reactive-custom-queries-chan)]
  182. (async/go-loop []
  183. (let [[f query] (async/<! chan)]
  184. (try
  185. (if (state/input-idle? (state/get-current-repo))
  186. (f)
  187. (do
  188. (async/<! (async/timeout 2000))
  189. (async/put! chan [f query])))
  190. (catch :default error
  191. (let [type :custom-query/failed]
  192. (js/console.error (str type "\n" query))
  193. (js/console.error error)))))
  194. (recur))
  195. chan))