react.cljs 13 KB

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