react.cljs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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 [frontend.db.conn :as conn]
  8. [frontend.state :as state]
  9. [frontend.date :as date]
  10. [frontend.util :as util :refer-macros [profile] :refer [react]]
  11. [clojure.string :as string]
  12. [frontend.config :as config]
  13. [datascript.core :as d]
  14. [lambdaisland.glogi :as log]
  15. [frontend.db.utils :as db-utils]))
  16. ;; Query atom of map of Key ([repo q inputs]) -> atom
  17. ;; TODO: replace with LRUCache, only keep the latest 20 or 50 items?
  18. (defonce query-state (atom {}))
  19. (def ^:dynamic *query-component*)
  20. ;; key -> components
  21. (defonce query-components (atom {}))
  22. (defn set-new-result!
  23. [k new-result]
  24. (when-let [result-atom (get-in @query-state [k :result])]
  25. (reset! result-atom new-result)))
  26. ;; KV
  27. (defn kv
  28. [key value]
  29. {:db/id -1
  30. :db/ident key
  31. key value})
  32. (defn remove-key!
  33. [repo-url key]
  34. (db-utils/transact! repo-url [[:db.fn/retractEntity [:db/ident key]]])
  35. (set-new-result! [repo-url :kv key] nil))
  36. (defn clear-query-state!
  37. []
  38. (reset! query-state {}))
  39. (defn clear-query-state-without-refs-and-embeds!
  40. []
  41. (let [state @query-state
  42. state (->> (filter (fn [[[_repo k] v]]
  43. (contains? #{:blocks :block/block :custom} k)) state)
  44. (into {}))]
  45. (reset! query-state state)))
  46. ;; TODO: Add components which subscribed to a specific query
  47. (defn add-q!
  48. [k query inputs result-atom transform-fn query-fn inputs-fn]
  49. (swap! query-state assoc k {:query query
  50. :inputs inputs
  51. :result result-atom
  52. :transform-fn transform-fn
  53. :query-fn query-fn
  54. :inputs-fn inputs-fn})
  55. result-atom)
  56. (defn remove-q!
  57. [k]
  58. (swap! query-state dissoc k))
  59. (defn add-query-component!
  60. [key component]
  61. (swap! query-components update key
  62. (fn [components]
  63. (distinct (conj components component)))))
  64. (defn remove-query-component!
  65. [component]
  66. (reset!
  67. query-components
  68. (->> (for [[k components] @query-components
  69. :let [new-components (remove #(= component %) components)]]
  70. (if (empty? new-components) ; no subscribed components
  71. (do (remove-q! k)
  72. nil)
  73. [k new-components]))
  74. (keep identity)
  75. (into {}))))
  76. (defn get-page-blocks-cache-atom
  77. [repo page-id]
  78. (:result (get @query-state [repo :page/blocks page-id])))
  79. (defn get-block-blocks-cache-atom
  80. [repo block-id]
  81. (:result (get @query-state [repo :block/block block-id])))
  82. ;; TODO: rename :custom to :query/custom
  83. (defn remove-custom-query!
  84. [repo query]
  85. (remove-q! [repo :custom query]))
  86. ;; Reactive query
  87. (defn query-entity-in-component
  88. ([id-or-lookup-ref]
  89. (db-utils/entity (state/get-current-repo) id-or-lookup-ref))
  90. ([repo id-or-lookup-ref]
  91. (let [k [:entity id-or-lookup-ref]
  92. result-atom (:result (get @query-state k))]
  93. (when-let [component *query-component*]
  94. (add-query-component! k component))
  95. (when-let [db (conn/get-conn repo)]
  96. (let [result (d/entity db id-or-lookup-ref)
  97. result-atom (or result-atom (atom nil))]
  98. (set! (.-state result-atom) result)
  99. (add-q! k nil nil result-atom identity identity identity))))))
  100. (defn q
  101. [repo k {:keys [use-cache? files-db? transform-fn query-fn inputs-fn]
  102. :or {use-cache? true
  103. files-db? false
  104. transform-fn identity}} query & inputs]
  105. (let [kv? (and (vector? k) (= :kv (first k)))
  106. k (vec (cons repo k))]
  107. (when-let [conn (if files-db?
  108. (when-let [files-conn (conn/get-files-conn repo)]
  109. (deref files-conn))
  110. (conn/get-conn repo))]
  111. (let [result-atom (:result (get @query-state k))]
  112. (when-let [component *query-component*]
  113. (add-query-component! k component))
  114. (if (and use-cache? result-atom)
  115. result-atom
  116. (let [result (cond
  117. query-fn
  118. (query-fn conn)
  119. inputs-fn
  120. (let [inputs (inputs-fn)]
  121. (apply d/q query conn inputs))
  122. kv?
  123. (d/entity conn (last k))
  124. (seq inputs)
  125. (apply d/q query conn inputs)
  126. :else
  127. (d/q query conn))
  128. result (transform-fn result)
  129. result-atom (or result-atom (atom nil))]
  130. ;; Don't notify watches now
  131. (set! (.-state result-atom) result)
  132. (add-q! k query inputs result-atom transform-fn query-fn inputs-fn)))))))
  133. ;; TODO: Extract several parts to handlers
  134. (defn get-current-page
  135. []
  136. (let [match (:route-match @state/state)
  137. route-name (get-in match [:data :name])
  138. tag? (= route-name :tag)
  139. page (case route-name
  140. :page
  141. (get-in match [:path-params :name])
  142. :file
  143. (get-in match [:path-params :path])
  144. :tag
  145. (get-in match [:path-params :name])
  146. (date/journal-name))]
  147. (when page
  148. (let [page-name (util/url-decode (string/lower-case page))]
  149. (db-utils/entity (if tag?
  150. [:tag/name page-name]
  151. [:page/name page-name]))))))
  152. (defn get-current-priority
  153. []
  154. (let [match (:route-match @state/state)
  155. route-name (get-in match [:data :name])]
  156. (when (= route-name :page)
  157. (when-let [page-name (get-in match [:path-params :name])]
  158. (and (contains? #{"a" "b" "c"} (string/lower-case page-name))
  159. (string/upper-case page-name))))))
  160. (defn get-current-marker
  161. []
  162. (let [match (:route-match @state/state)
  163. route-name (get-in match [:data :name])]
  164. (when (= route-name :page)
  165. (when-let [page-name (get-in match [:path-params :name])]
  166. (and (util/marker? page-name)
  167. (string/upper-case page-name))))))
  168. (defn get-handler-keys
  169. [{:keys [key data]}]
  170. (cond
  171. (coll? key)
  172. [key]
  173. :else
  174. (case key
  175. (:block/change :block/insert)
  176. (when-let [blocks (seq data)]
  177. (let [pre-block? (:block/pre-block? (first blocks))
  178. current-priority (get-current-priority)
  179. current-marker (get-current-marker)
  180. current-page-id (:db/id (get-current-page))
  181. {:block/keys [page]} (first blocks)
  182. handler-keys (->>
  183. (util/concat-without-nil
  184. (mapcat
  185. (fn [block]
  186. (when-let [page-id (:db/id (:block/page block))]
  187. [[:blocks (:block/uuid block)]
  188. [:page/blocks page-id]
  189. [:page/ref-pages page-id]]))
  190. blocks)
  191. (when pre-block?
  192. [[:contents]
  193. [:page/published]])
  194. ;; affected priority
  195. (when current-priority
  196. [[:priority/blocks current-priority]])
  197. (when current-marker
  198. [[:marker/blocks current-marker]])
  199. (when current-page-id
  200. [[:page/ref-pages current-page-id]
  201. [:page/refed-blocks current-page-id]
  202. [:page/mentioned-pages current-page-id]])
  203. ;; refed-pages
  204. (apply concat
  205. (for [{:block/keys [ref-pages]} blocks]
  206. (map (fn [page]
  207. (when-let [page (db-utils/entity [:page/name (:page/name page)])]
  208. [:page/refed-blocks (:db/id page)]))
  209. ref-pages)))
  210. ;; refed-blocks
  211. (apply concat
  212. (for [{:block/keys [ref-blocks]} blocks]
  213. (map (fn [ref-block]
  214. [:block/refed-blocks (last ref-block)])
  215. ref-blocks))))
  216. (distinct))
  217. refed-pages (map
  218. (fn [[k page-id]]
  219. (if (= k :page/refed-blocks)
  220. [:page/ref-pages page-id]))
  221. handler-keys)
  222. custom-queries (some->>
  223. (filter (fn [v]
  224. (and (= (first v) (state/get-current-repo))
  225. (= (second v) :custom)))
  226. (keys @query-state))
  227. (map (fn [v]
  228. (vec (drop 1 v)))))
  229. block-blocks (some->>
  230. (filter (fn [v]
  231. (and (= (first v) (state/get-current-repo))
  232. (= (second v) :block/block)))
  233. (keys @query-state))
  234. (map (fn [v]
  235. (vec (drop 1 v)))))]
  236. (->>
  237. (util/concat-without-nil
  238. handler-keys
  239. refed-pages
  240. custom-queries
  241. block-blocks)
  242. distinct)))
  243. [[key]])))
  244. (defn transact-react!
  245. [repo-url tx-data {:keys [key data files-db?] :as handler-opts
  246. :or {files-db? false}}]
  247. (when-not config/publishing?
  248. (try
  249. (let [repo-url (or repo-url (state/get-current-repo))
  250. tx-data (->> (util/remove-nils tx-data)
  251. (remove nil?))
  252. get-conn (fn [] (if files-db?
  253. (conn/get-files-conn repo-url)
  254. (conn/get-conn repo-url false)))]
  255. (when (and (seq tx-data) (get-conn))
  256. (let [tx-result (profile "Transact!" (d/transact! (get-conn) (vec tx-data)))
  257. db (:db-after tx-result)
  258. handler-keys (get-handler-keys handler-opts)]
  259. (doseq [handler-key handler-keys]
  260. (let [handler-key (vec (cons repo-url handler-key))]
  261. (when-let [cache (get @query-state handler-key)]
  262. (let [{:keys [query inputs transform-fn query-fn inputs-fn]} cache]
  263. (when (or query query-fn)
  264. (let [new-result (->
  265. (cond
  266. query-fn
  267. (profile
  268. "Query:"
  269. (doall (query-fn db)))
  270. inputs-fn
  271. (let [inputs (inputs-fn)]
  272. (apply d/q query db inputs))
  273. (keyword? query)
  274. (db-utils/get-key-value repo-url query)
  275. (seq inputs)
  276. (apply d/q query db inputs)
  277. :else
  278. (d/q query db))
  279. transform-fn)]
  280. (set-new-result! handler-key new-result))))))))))
  281. (catch js/Error e
  282. ;; FIXME: check error type and notice user
  283. (log/error :db/transact! e)))))
  284. (defn set-key-value
  285. [repo-url key value]
  286. (if value
  287. (transact-react! repo-url [(kv key value)]
  288. {:key [:kv key]})
  289. (remove-key! repo-url key)))
  290. (defn sub-key-value
  291. ([key]
  292. (sub-key-value (state/get-current-repo) key))
  293. ([repo-url key]
  294. (when (conn/get-conn repo-url)
  295. (-> (q repo-url [:kv key] {} key key)
  296. react
  297. key))))
  298. (defn set-file-content!
  299. [repo path content]
  300. (when (and repo path)
  301. (let [tx-data {:file/path path
  302. :file/content content
  303. :file/last-modified-at (util/time-ms)}
  304. tx-data (if (config/local-db? repo)
  305. (dissoc tx-data :file/last-modified-at)
  306. tx-data)]
  307. (transact-react!
  308. repo
  309. [tx-data]
  310. {:key [:file/content path]
  311. :files-db? true}))))