react.cljs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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 [datascript.core :as d]
  8. [frontend.date :as date]
  9. [frontend.db.conn :as conn]
  10. [frontend.db.utils :as db-utils]
  11. [frontend.state :as state]
  12. [frontend.util :as util :refer [react]]
  13. [cljs.spec.alpha :as s]
  14. [clojure.core.async :as async]))
  15. ;;; keywords specs for reactive query, used by `react/q` calls
  16. ;; ::block
  17. ;; pull-block react-query
  18. (s/def ::block (s/tuple #(= ::block %) uuid?))
  19. ;; ::page-blocks
  20. ;; get page-blocks react-query
  21. (s/def ::page-blocks (s/tuple #(= ::page-blocks %) int?))
  22. ;; ::block-and-children
  23. ;; get block&children react-query
  24. (s/def ::block-and-children (s/tuple #(= ::block-and-children %) uuid?))
  25. (s/def ::block-direct-children (s/tuple #(= ::block-direct-children %) uuid?))
  26. ;; ::journals
  27. ;; get journal-list react-query
  28. (s/def ::journals (s/tuple #(= ::journals %)))
  29. ;; ::page->pages
  30. ;; get PAGES referenced by PAGE
  31. (s/def ::page->pages (s/tuple #(= ::page->pages %) int?))
  32. ;; ::page<-pages
  33. ;; get PAGES referencing PAGE
  34. (s/def ::page<-pages (s/tuple #(= ::page<-pages %) int?))
  35. ;; ::page<-blocks-or-block<-blocks
  36. ;; get BLOCKS referencing PAGE or BLOCK
  37. (s/def ::page<-blocks-or-block<-blocks
  38. (s/tuple #(= ::page<-blocks-or-block<-blocks %) int?))
  39. ;; FIXME: this react-query has performance issues
  40. (s/def ::page-unlinked-refs (s/tuple #(= ::page-unlinked-refs %) int?))
  41. ;; ::block<-block-ids
  42. ;; get BLOCK-IDS referencing BLOCK
  43. (s/def ::block<-block-ids (s/tuple #(= ::block<-block-ids %) int?))
  44. ;; custom react-query
  45. (s/def ::custom any?)
  46. (s/def ::react-query-keys (s/or :block ::block
  47. :page-blocks ::page-blocks
  48. :block-and-children ::block-and-children
  49. :block-direct-children ::block-direct-children
  50. :journals ::journals
  51. :page->pages ::page->pages
  52. :page<-pages ::page<-pages
  53. :page<-blocks-or-block<-blocks ::page<-blocks-or-block<-blocks
  54. :page-unlinked-refs ::page-unlinked-refs
  55. :block<-block-ids ::block<-block-ids
  56. :custom ::custom))
  57. (s/def ::affected-keys (s/coll-of ::react-query-keys))
  58. ;; Query atom of map of Key ([repo q inputs]) -> atom
  59. ;; TODO: replace with LRUCache, only keep the latest 20 or 50 items?
  60. (defonce query-state (atom {}))
  61. (def ^:dynamic *query-component*)
  62. ;; component -> query-key
  63. (defonce query-components (atom {}))
  64. (defn set-new-result!
  65. [k new-result]
  66. (when-let [result-atom (get-in @query-state [k :result])]
  67. (reset! result-atom new-result)))
  68. (defn swap-new-result!
  69. [k f]
  70. (when-let [result-atom (get-in @query-state [k :result])]
  71. (swap! result-atom f)))
  72. (defn kv
  73. [key value]
  74. {:db/id -1
  75. :db/ident key
  76. key value})
  77. (defn remove-key!
  78. [repo-url key]
  79. (db-utils/transact! repo-url [[:db.fn/retractEntity [:db/ident key]]])
  80. (set-new-result! [repo-url :kv key] nil))
  81. (defn clear-query-state!
  82. []
  83. (reset! query-state {}))
  84. (defn clear-query-state-without-refs-and-embeds!
  85. []
  86. (let [state @query-state
  87. state (->> (filter (fn [[[_repo k] _v]]
  88. (contains? #{:blocks :block/block :custom} k)) state)
  89. (into {}))]
  90. (reset! query-state state)))
  91. (defn add-q!
  92. [k query inputs result-atom transform-fn query-fn inputs-fn]
  93. (swap! query-state assoc k {:query query
  94. :inputs inputs
  95. :result result-atom
  96. :transform-fn transform-fn
  97. :query-fn query-fn
  98. :inputs-fn inputs-fn})
  99. result-atom)
  100. (defn remove-q!
  101. [k]
  102. (swap! query-state dissoc k))
  103. (defn add-query-component!
  104. [key component]
  105. (when (and key component)
  106. (swap! query-components assoc component key)))
  107. (defn remove-query-component!
  108. [component]
  109. (when-let [query (get @query-components component)]
  110. (let [matched-queries (filter #(= query %) (vals @query-components))]
  111. (when (= 1 (count matched-queries))
  112. (remove-q! query))))
  113. (swap! query-components dissoc component))
  114. ;; TODO: rename :custom to :query/custom
  115. (defn remove-custom-query!
  116. [repo query]
  117. (remove-q! [repo :custom query]))
  118. ;; Reactive query
  119. (defn get-query-cached-result
  120. [k]
  121. (:result (get @query-state k)))
  122. (defn q
  123. [repo k {:keys [use-cache? transform-fn query-fn inputs-fn disable-reactive?]
  124. :or {use-cache? true
  125. transform-fn identity}} query & inputs]
  126. {:pre [(s/valid? ::react-query-keys k)]}
  127. (let [kv? (and (vector? k) (= :kv (first k)))
  128. k (vec (cons repo k))]
  129. (when-let [db (conn/get-db repo)]
  130. (let [result-atom (get-query-cached-result k)]
  131. (when-let [component *query-component*]
  132. (add-query-component! k component))
  133. (if (and use-cache? result-atom)
  134. result-atom
  135. (let [result (cond
  136. query-fn
  137. (query-fn db nil nil)
  138. inputs-fn
  139. (let [inputs (inputs-fn)]
  140. (apply d/q query db inputs))
  141. kv?
  142. (d/entity db (last k))
  143. (seq inputs)
  144. (apply d/q query db inputs)
  145. :else
  146. (d/q query db))
  147. result (transform-fn result)
  148. result-atom (or result-atom (atom nil))]
  149. ;; Don't notify watches now
  150. (set! (.-state result-atom) result)
  151. (if disable-reactive?
  152. result-atom
  153. (add-q! k query inputs result-atom transform-fn query-fn inputs-fn))))))))
  154. ;; TODO: Extract several parts to handlers
  155. (defn get-current-page
  156. []
  157. (let [match (:route-match @state/state)
  158. route-name (get-in match [:data :name])
  159. page (case route-name
  160. :page
  161. (get-in match [:path-params :name])
  162. :file
  163. (get-in match [:path-params :path])
  164. (date/journal-name))]
  165. (when page
  166. (let [page-name (util/page-name-sanity-lc page)]
  167. (db-utils/entity [:block/name page-name])))))
  168. (defn get-affected-queries-keys
  169. "Get affected queries through transaction datoms."
  170. [{:keys [tx-data db-before]}]
  171. {:post [(s/valid? ::affected-keys %)]}
  172. (let [blocks (->> (filter (fn [datom] (contains? #{:block/left :block/parent :block/page} (:a datom))) tx-data)
  173. (map :v)
  174. (distinct))
  175. refs (->> (filter (fn [datom] (= :block/refs (:a datom))) tx-data)
  176. (map :v)
  177. (distinct))
  178. other-blocks (->> (filter (fn [datom] (= "block" (namespace (:a datom)))) tx-data)
  179. (map :e))
  180. blocks (-> (concat blocks other-blocks) distinct)
  181. affected-keys (concat
  182. (mapcat
  183. (fn [block-id]
  184. (let [block-id (if (and (string? block-id) (util/uuid-string? block-id))
  185. [:block/uuid block-id]
  186. block-id)]
  187. (when-let [block (db-utils/entity block-id)]
  188. (let [page-id (or
  189. (when (:block/name block) (:db/id block))
  190. (:db/id (:block/page block)))
  191. blocks [[::block (:block/uuid block)]]
  192. others (when page-id
  193. (let [db-after-parent-uuid (:block/uuid (:block/parent block))
  194. db-before-parent-uuid (:block/uuid (:block/parent (d/entity db-before
  195. [:block/uuid (:block/uuid block)])))]
  196. [[::page-blocks page-id]
  197. [::page->pages page-id]
  198. [::block-direct-children db-after-parent-uuid]
  199. (when (and db-before-parent-uuid
  200. (not= db-before-parent-uuid db-after-parent-uuid))
  201. [::block-direct-children db-before-parent-uuid])]))]
  202. (concat blocks others)))))
  203. blocks)
  204. (when-let [current-page-id (:db/id (get-current-page))]
  205. [[::page->pages current-page-id]
  206. [::page<-pages current-page-id]])
  207. (map (fn [ref]
  208. (let [entity (db-utils/entity ref)]
  209. (if (:block/name entity) ; page
  210. [::page-blocks ref]
  211. [::page-blocks (:db/id (:block/page entity))])))
  212. refs))
  213. others (->>
  214. (keys @query-state)
  215. (filter (fn [ks]
  216. (contains? #{::block-and-children
  217. ::page<-blocks-or-block<-blocks}
  218. (second ks))))
  219. (map (fn [v] (vec (rest v)))))]
  220. (->>
  221. (util/concat-without-nil
  222. affected-keys
  223. others)
  224. set)))
  225. (defn- execute-query!
  226. [graph db k tx {:keys [query inputs transform-fn query-fn inputs-fn result]}]
  227. (let [new-result (->
  228. (cond
  229. query-fn
  230. (let [result (query-fn db tx result)]
  231. (if (coll? result)
  232. (doall result)
  233. result))
  234. inputs-fn
  235. (let [inputs (inputs-fn)]
  236. (apply d/q query db inputs))
  237. (keyword? query)
  238. (db-utils/get-key-value graph query)
  239. (seq inputs)
  240. (apply d/q query db inputs)
  241. :else
  242. (d/q query db))
  243. transform-fn)]
  244. (when-not (= new-result result)
  245. (set-new-result! k new-result))))
  246. (defn refresh!
  247. "Re-compute corresponding queries (from tx) and refresh the related react components."
  248. [repo-url {:keys [tx-data tx-meta] :as tx}]
  249. (when (and repo-url
  250. (seq tx-data)
  251. (not (:skip-refresh? tx-meta)))
  252. (let [db (conn/get-db repo-url)
  253. affected-keys (get-affected-queries-keys tx)]
  254. (doseq [[k cache] @query-state]
  255. (let [custom? (= :custom (second k))
  256. kv? (= :kv (second k))]
  257. (when (and
  258. (= (first k) repo-url)
  259. (or (get affected-keys (vec (rest k)))
  260. custom?
  261. kv?))
  262. (let [{:keys [query query-fn]} cache]
  263. (when (or query query-fn)
  264. (try
  265. (let [f #(execute-query! repo-url db k tx cache)]
  266. ;; Detects whether user is editing in a custom query, if so, execute the query immediately
  267. (if (and custom?
  268. ;; modifying during cards review need to be executed immediately
  269. (not (:cards-query? (meta query)))
  270. (not (state/edit-in-query-component)))
  271. (async/put! (state/get-reactive-custom-queries-chan) [f query])
  272. (f)))
  273. (catch js/Error e
  274. (js/console.error e)))))))))))
  275. (defn set-key-value
  276. [repo-url key value]
  277. (if value
  278. (db-utils/transact! repo-url [(kv key value)])
  279. (remove-key! repo-url key)))
  280. (defn sub-key-value
  281. ([key]
  282. (sub-key-value (state/get-current-repo) key))
  283. ([repo-url key]
  284. (when (conn/get-db repo-url)
  285. (let [m (some-> (q repo-url [:kv key] {} key key) react)]
  286. (if-let [result (get m key)]
  287. result
  288. m)))))
  289. (defn run-custom-queries-when-idle!
  290. []
  291. (let [chan (state/get-reactive-custom-queries-chan)]
  292. (async/go-loop []
  293. (let [[f query] (async/<! chan)]
  294. (try
  295. (if (state/input-idle? (state/get-current-repo))
  296. (f)
  297. (do
  298. (async/<! (async/timeout 2000))
  299. (async/put! chan [f query])))
  300. (catch js/Error error
  301. (let [type :custom-query/failed]
  302. (js/console.error (str type "\n" query))
  303. (js/console.error error)))))
  304. (recur))
  305. chan))