query.cljs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. (ns frontend.components.query
  2. (:require [rum.core :as rum]
  3. [frontend.ui :as ui]
  4. [frontend.util :as util]
  5. [frontend.state :as state]
  6. [frontend.db :as db]
  7. [frontend.db-mixins :as db-mixins]
  8. [clojure.string :as string]
  9. [frontend.db.query-dsl :as query-dsl]
  10. [frontend.components.query-table :as query-table]
  11. [frontend.components.query.result :as query-result]
  12. [lambdaisland.glogi :as log]
  13. [frontend.extensions.sci :as sci]
  14. [frontend.handler.editor :as editor-handler]
  15. [frontend.handler.editor.property :as editor-property]
  16. [logseq.graph-parser.util :as gp-util]))
  17. (defn built-in-custom-query?
  18. [title]
  19. (let [queries (get-in (state/sub-config) [:default-queries :journals])]
  20. (when (seq queries)
  21. (boolean (some #(= % title) (map :title queries))))))
  22. (rum/defc query-refresh-button
  23. [query-time {:keys [on-mouse-down full-text-search?]}]
  24. (ui/tippy
  25. {:html [:div
  26. [:p
  27. (if full-text-search?
  28. [:span "Full-text search results will not be refreshed automatically."]
  29. [:span (str "This query takes " (int query-time) "ms to finish, it's a bit slow so that auto refresh is disabled.")])]
  30. [:p
  31. "Click the refresh button instead if you want to see the latest result."]]
  32. :interactive true
  33. :popperOptions {:modifiers {:preventOverflow
  34. {:enabled true
  35. :boundariesElement "viewport"}}}
  36. :arrow true}
  37. [:a.fade-link.flex
  38. {:on-mouse-down on-mouse-down}
  39. (ui/icon "refresh" {:style {:font-size 20}})]))
  40. (rum/defcs custom-query-inner < rum/reactive
  41. [state config {:keys [query children? breadcrumb-show?]}
  42. {:keys [query-error-atom
  43. current-block
  44. table?
  45. dsl-query?
  46. page-list?
  47. view-f
  48. result
  49. group-by-page?]}]
  50. (let [{:keys [->hiccup ->elem inline-text page-cp map-inline inline]} config
  51. *query-error query-error-atom
  52. only-blocks? (:block/uuid (first result))
  53. blocks-grouped-by-page? (and group-by-page?
  54. (seq result)
  55. (coll? (first result))
  56. (:block/name (ffirst result))
  57. (:block/uuid (first (second (first result))))
  58. true)]
  59. (if @*query-error
  60. (do
  61. (log/error :exception @*query-error)
  62. [:div.warning.my-1 "Query failed: "
  63. [:p (.-message @*query-error)]])
  64. [:div.custom-query-results
  65. (cond
  66. (and (seq result) view-f)
  67. (let [result (try
  68. (sci/call-fn view-f result)
  69. (catch :default error
  70. (log/error :custom-view-failed {:error error
  71. :result result})
  72. [:div "Custom view failed: "
  73. (str error)]))]
  74. (util/hiccup-keywordize result))
  75. page-list?
  76. (query-table/result-table config current-block result {:page? true} map-inline page-cp ->elem inline-text inline)
  77. table?
  78. (query-table/result-table config current-block result {:page? false} map-inline page-cp ->elem inline-text inline)
  79. (and (seq result) (or only-blocks? blocks-grouped-by-page?))
  80. (->hiccup result
  81. (cond-> (assoc config
  82. :custom-query? true
  83. :dsl-query? dsl-query?
  84. :query query
  85. :breadcrumb-show? (if (some? breadcrumb-show?)
  86. breadcrumb-show?
  87. true)
  88. :group-by-page? blocks-grouped-by-page?
  89. :ref? true)
  90. children?
  91. (assoc :ref? true))
  92. {:style {:margin-top "0.25rem"
  93. :margin-left "0.25rem"}})
  94. (seq result)
  95. (let [result (->>
  96. (for [record result]
  97. (if (map? record)
  98. (str (util/pp-str record) "\n")
  99. record))
  100. (remove nil?))]
  101. (when (seq result)
  102. [:ul
  103. (for [item result]
  104. [:li (str item)])]))
  105. (or (string/blank? query)
  106. (= query "(and)"))
  107. nil
  108. :else
  109. [:div.text-sm.mt-2.opacity-90 "No matched result"])])))
  110. (rum/defc query-title
  111. [config title {:keys [result-count]}]
  112. (let [inline-text (:inline-text config)]
  113. [:div.custom-query-title.flex.justify-between.w-full
  114. [:span.title-text (cond
  115. (vector? title) title
  116. (string? title) (inline-text config
  117. (get-in config [:block :block/format] :markdown)
  118. title)
  119. :else title)]
  120. (when result-count
  121. [:span.opacity-60.text-sm.ml-2.results-count
  122. (str result-count (if (> result-count 1) " results" " result"))])]))
  123. (rum/defcs ^:large-vars/cleanup-todo custom-query* < rum/reactive rum/static db-mixins/query
  124. {:init (fn [state]
  125. (let [[config {:keys [title collapsed?]}] (:rum/args state)
  126. built-in? (built-in-custom-query? title)
  127. dsl-query? (:dsl-query? config)
  128. current-block-uuid (or (:block/uuid (:block config))
  129. (:block/uuid config))]
  130. (when-not (or built-in? dsl-query?)
  131. (when collapsed?
  132. (editor-handler/collapse-block! current-block-uuid))))
  133. (assoc state :query-error (atom nil)))}
  134. [state config {:keys [title builder query view collapsed? table-view?] :as q} *query-triggered?]
  135. (let [*query-error (:query-error state)
  136. built-in? (built-in-custom-query? title)
  137. dsl-query? (:dsl-query? config)
  138. current-block-uuid (or (:block/uuid (:block config))
  139. (:block/uuid config))
  140. current-block (db/entity [:block/uuid current-block-uuid])
  141. temp-collapsed? (state/sub-collapsed current-block-uuid)
  142. collapsed?' (if (some? temp-collapsed?)
  143. temp-collapsed?
  144. (or
  145. collapsed?
  146. (:block/collapsed? current-block)))
  147. built-in-collapsed? (and collapsed? built-in?)
  148. table? (or table-view?
  149. (get-in current-block [:block/properties :query-table])
  150. (and (string? query) (string/ends-with? (string/trim query) "table")))
  151. view-fn (if (keyword? view) (get-in (state/sub-config) [:query/views view]) view)
  152. view-f (and view-fn (sci/eval-string (pr-str view-fn)))
  153. dsl-page-query? (and dsl-query?
  154. (false? (:blocks? (query-dsl/parse-query query))))
  155. full-text-search? (and dsl-query?
  156. (util/electron?)
  157. (symbol? (gp-util/safe-read-string query)))
  158. result (when (or built-in-collapsed? (not collapsed?'))
  159. (query-result/get-query-result state config *query-error *query-triggered? current-block-uuid q {:table? table?}))
  160. query-time (:query-time (meta result))
  161. page-list? (and (seq result)
  162. (some? (:block/name (first result))))
  163. opts {:query-error-atom *query-error
  164. :current-block current-block
  165. :dsl-query? dsl-query?
  166. :table? table?
  167. :view-f view-f
  168. :page-list? page-list?
  169. :result result
  170. :group-by-page? (query-result/get-group-by-page q {:table? table?})}]
  171. (if (:custom-query? config)
  172. [:code (if dsl-query?
  173. (util/format "{{query %s}}" query)
  174. "{{query hidden}}")]
  175. (when-not (and built-in? (empty? result))
  176. [:div.custom-query (get config :attr {})
  177. (when-not built-in?
  178. [:div.th
  179. (if dsl-query?
  180. [:div.flex.flex-1.flex-row
  181. (ui/icon "search" {:size 14})
  182. [:div.ml-1 (str "Live query" (when dsl-page-query? " for pages"))]]
  183. [:div {:style {:font-size "initial"}} title])
  184. (when (or (not dsl-query?) (not collapsed?'))
  185. [:div.flex.flex-row.items-center.fade-in
  186. (when (> (count result) 0)
  187. [:span.results-count
  188. (let [result-count (if (and (not table?) (map? result))
  189. (apply + (map (comp count val) result))
  190. (count result))]
  191. (str result-count (if (> result-count 1) " results" " result")))])
  192. (when (and current-block (not view-f) (nil? table-view?) (not page-list?))
  193. (if table?
  194. [:a.flex.ml-1.fade-link {:title "Switch to list view"
  195. :on-click (fn [] (editor-property/set-block-property! current-block-uuid
  196. "query-table"
  197. false))}
  198. (ui/icon "list" {:style {:font-size 20}})]
  199. [:a.flex.ml-1.fade-link {:title "Switch to table view"
  200. :on-click (fn [] (editor-property/set-block-property! current-block-uuid
  201. "query-table"
  202. true))}
  203. (ui/icon "table" {:style {:font-size 20}})]))
  204. [:a.flex.ml-1.fade-link
  205. {:title "Setting properties"
  206. :on-click (fn []
  207. (let [all-keys (query-table/get-keys result page-list?)]
  208. (state/pub-event! [:modal/set-query-properties current-block all-keys])))}
  209. (ui/icon "settings" {:style {:font-size 20}})]
  210. [:div.ml-1
  211. (when (or full-text-search?
  212. (and query-time (> query-time 50)))
  213. (query-refresh-button query-time {:full-text-search? full-text-search?
  214. :on-mouse-down (fn [e]
  215. (util/stop e)
  216. (query-result/trigger-custom-query! state *query-error *query-triggered?))}))]])])
  217. (when dsl-query? builder)
  218. (if built-in?
  219. [:div {:style {:margin-left 2}}
  220. (ui/foldable
  221. (query-title config title {:result-count (count result)})
  222. (fn []
  223. (custom-query-inner config q opts))
  224. {:default-collapsed? collapsed?
  225. :title-trigger? true})]
  226. [:div.bd
  227. (when-not collapsed?'
  228. (custom-query-inner config q opts))])]))))
  229. (rum/defcs custom-query < rum/static
  230. (rum/local false ::query-triggered?)
  231. [state config q]
  232. (ui/catch-error
  233. (ui/block-error "Query Error:" {:content (:query q)})
  234. (ui/lazy-visible
  235. (fn []
  236. (custom-query* config q (::query-triggered? state)))
  237. {:debug-id q})))