objects.cljs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. (ns frontend.components.objects
  2. "Tagged objects"
  3. (:require [logseq.shui.ui :as shui]
  4. [rum.core :as rum]
  5. [frontend.util :as util]
  6. [frontend.ui :as ui]
  7. [clojure.string :as string]
  8. [frontend.components.block :as component-block]
  9. [frontend.components.property.value :as pv]
  10. [frontend.components.select :as select]
  11. [frontend.state :as state]
  12. [frontend.date :as date]
  13. [goog.object :as gobj]
  14. [goog.dom :as gdom]
  15. [cljs-bean.core :as bean]
  16. [promesa.core :as p]
  17. [logseq.db :as ldb]
  18. [frontend.db :as db]
  19. [frontend.search.fuzzy :as fuzzy-search]
  20. [logseq.outliner.property :as outliner-property]
  21. [frontend.mixins :as mixins]
  22. [logseq.db.frontend.property :as db-property]
  23. [logseq.db.frontend.property.type :as db-property-type]
  24. [clojure.set :as set]
  25. [datascript.impl.entity :as de]
  26. [cljs-time.core :as t]
  27. [cljs-time.coerce :as tc]))
  28. (defn header-checkbox [{:keys [selected-all? selected-some? toggle-selected-all!]}]
  29. (shui/checkbox
  30. {:checked (or selected-all? (and selected-some? "indeterminate"))
  31. :on-checked-change toggle-selected-all!
  32. :aria-label "Select all"}))
  33. (defn row-checkbox [{:keys [row-selected? row-toggle-selected!]} row _column]
  34. (shui/checkbox
  35. {:checked (row-selected? row)
  36. :on-checked-change (fn [v] (row-toggle-selected! row v))
  37. :aria-label "Select row"}))
  38. (defn- header-cp
  39. [{:keys [column-toggle-sorting! state]} column]
  40. (let [sorting (:sorting state)
  41. [asc?] (some (fn [item] (when (= (:id item) (:id column))
  42. (when-some [asc? (:asc? item)]
  43. [asc?]))) sorting)]
  44. (shui/button
  45. {:variant "text"
  46. :class "!pl-0 !py-0 hover:text-foreground"
  47. :onClick #(column-toggle-sorting! column)}
  48. (:name column)
  49. (case asc?
  50. true
  51. (ui/icon "arrow-up")
  52. false
  53. (ui/icon "arrow-down")
  54. nil))))
  55. (defn- timestamp-cell-cp
  56. [_table row column]
  57. (some-> (get row (:id column))
  58. date/int->local-time-2))
  59. (defn- get-property-value-content
  60. [entity]
  61. (when entity
  62. (if (map? entity)
  63. (db-property/property-value-content entity)
  64. (str entity))))
  65. (defn- get-property-value-for-search
  66. [block property]
  67. (let [type (get-in property [:block/schema :type])
  68. many? (= :db.cardinality/many (get property :db/cardinality))
  69. ref-types (into db-property-type/value-ref-property-types #{:entity})
  70. number-type? (= :number type)
  71. v (get block (:db/ident property))
  72. v' (if many? v [v])
  73. col (->> (if (ref-types type) (map db-property/property-value-content v') v')
  74. (remove nil?))]
  75. (if number-type?
  76. (reduce + (filter number? col))
  77. (string/join ", " col))))
  78. (defn- build-columns
  79. [class config]
  80. (let [properties (outliner-property/get-class-properties class)]
  81. (concat
  82. [{:id :select
  83. :name "Select"
  84. :header (fn [table _column] (header-checkbox table))
  85. :cell (fn [table row column] (row-checkbox table row column))
  86. :column-list? false}
  87. {:id :object/name
  88. :name "Name"
  89. :type :string
  90. :header header-cp
  91. :cell (fn [_table row _column]
  92. [:div.primary-cell
  93. (component-block/block-container (assoc config :table? true) row)])
  94. :disable-hide? true}]
  95. (map
  96. (fn [property]
  97. {:id (:db/ident property)
  98. :name (:block/original-name property)
  99. :header header-cp
  100. :cell (fn [_table row _column]
  101. (pv/property-value row property (get row (:db/ident property)) {}))
  102. :get-value (fn [row] (get-property-value-for-search row property))})
  103. properties)
  104. [{:id :block/created-at
  105. :name "Created At"
  106. :type :date-time
  107. :header header-cp
  108. :cell timestamp-cell-cp}
  109. {:id :block/updated-at
  110. :name "Updated At"
  111. :type :date-time
  112. :header header-cp
  113. :cell timestamp-cell-cp}])))
  114. (defn- get-all-objects
  115. [class]
  116. ;; FIXME: async
  117. (:block/_tags class))
  118. (rum/defc more-actions
  119. [columns {:keys [column-visible? column-toggle-visiblity]}]
  120. (shui/dropdown-menu
  121. (shui/dropdown-menu-trigger
  122. {:asChild true}
  123. (shui/button
  124. {:variant "ghost"
  125. :class "text-muted-foreground !px-1"
  126. :size :sm
  127. :on-click #()}
  128. (ui/icon "dots")))
  129. (shui/dropdown-menu-content
  130. {:align "end"}
  131. (shui/dropdown-menu-group
  132. (shui/dropdown-menu-sub
  133. (shui/dropdown-menu-sub-trigger
  134. "Properties")
  135. (shui/dropdown-menu-sub-content
  136. (for [column (remove #(or (false? (:column-list? %))
  137. (:disable-hide? %)) columns)]
  138. (shui/dropdown-menu-checkbox-item
  139. {:key (str (:id column))
  140. :className "capitalize"
  141. :checked (column-visible? column)
  142. :onCheckedChange #(column-toggle-visiblity column %)
  143. :onSelect (fn [e] (.preventDefault e))}
  144. (:name column)))))))))
  145. (defn table-header
  146. [table columns]
  147. (shui/table-row
  148. {:class "bg-gray-01 shadow"}
  149. (for [column columns]
  150. (let [style (case (:id column)
  151. :block/original-name
  152. {}
  153. :select
  154. {:width 32}
  155. {:width 180})]
  156. (shui/table-head
  157. {:key (str (:id column))
  158. :style style}
  159. (let [header-fn (:header column)]
  160. (if (fn? header-fn)
  161. (header-fn table column)
  162. header-fn)))))))
  163. (rum/defc table-row < rum/reactive
  164. [{:keys [row-selected?] :as table} rows columns props]
  165. (let [idx (gobj/get props "data-index")
  166. row (nth rows idx)
  167. row (if-let [db-id (:db/id row)]
  168. (db/sub-block db-id)
  169. row)]
  170. (shui/table-row
  171. (merge
  172. (bean/->clj props)
  173. {:key (str (:id row))
  174. :data-state (when (row-selected? row) "selected")})
  175. (for [column columns]
  176. (let [id (str (:id row) "-" (:id column))
  177. render (get column :cell)]
  178. (shui/table-cell
  179. {:key id}
  180. (render table row column)))))))
  181. (rum/defc search
  182. [input {:keys [on-change]}]
  183. (let [[show-input? set-show-input!] (rum/use-state false)]
  184. (if show-input?
  185. (shui/input
  186. {:placeholder "Type to search"
  187. :auto-focus true
  188. :value input
  189. :onChange (fn [e]
  190. (let [value (util/evalue e)]
  191. (on-change value)))
  192. :on-key-down (fn [e]
  193. (when (= "Escape" (util/ekey e))
  194. (set-show-input! false)))
  195. :class "max-w-sm !h-7 !py-0 border-none focus-visible:ring-0 focus-visible:ring-offset-0"})
  196. (shui/button
  197. {:variant "ghost"
  198. ;; FIXME: remove ring when focused
  199. :class "text-muted-foreground !px-1"
  200. :size :sm
  201. :on-click #(set-show-input! true)}
  202. (ui/icon "search")))))
  203. (defn- property-ref-type?
  204. [property]
  205. (let [schema (:block/schema property)
  206. type (:type schema)]
  207. (db-property-type/ref-property-types type)))
  208. (defn- get-property-values
  209. [rows property]
  210. (let [property-ident (:db/ident property)
  211. values (->> (mapcat (fn [e] (let [v (get e property-ident)]
  212. (if (set? v) v #{v}))) rows)
  213. (remove nil?)
  214. (distinct))]
  215. (->>
  216. (map (fn [e]
  217. {:label (get-property-value-content e)
  218. :value e})
  219. values)
  220. (sort-by :label))))
  221. (defn timestamp-property?
  222. [ident]
  223. (contains? #{:block/created-at :block/updated-at} ident))
  224. (def timestamp-options
  225. [{:value "1 week ago"
  226. :label "1 week ago"}
  227. {:value "1 month ago"
  228. :label "1 month ago"}
  229. {:value "3 months ago"
  230. :label "3 months ago"}
  231. {:value "1 year ago"
  232. :label "1 year ago"}
  233. ;; TODO: support date picker
  234. ;; {:value "Custom time"
  235. ;; :label "Custom time"}
  236. ])
  237. (defn- get-timestamp
  238. [value]
  239. (let [now (t/now)
  240. f t/minus]
  241. (case value
  242. "1 week ago"
  243. (tc/to-long (f now (t/weeks 1)))
  244. "1 month ago"
  245. (tc/to-long (f now (t/months 1)))
  246. "3 months ago"
  247. (tc/to-long (f now (t/months 3)))
  248. "1 year ago"
  249. (tc/to-long (f now (t/years 1)))
  250. nil)))
  251. (rum/defc filter-property < rum/static
  252. [columns {:keys [data-fns] :as table}]
  253. (let [[property set-property!] (rum/use-state nil)
  254. timestamp? (timestamp-property? (:db/ident property))
  255. set-filters! (:set-filters! data-fns)
  256. filters (get-in table [:state :filters])
  257. columns (remove #(false? (:column-list? %)) columns)
  258. items (map (fn [column]
  259. {:label (:name column)
  260. :value column}) columns)
  261. option {:input-default-placeholder "Filter"
  262. :input-opts {:class "!px-3 !py-1"}
  263. :items items
  264. :extract-fn :label
  265. :extract-chosen-fn :value
  266. :on-chosen (fn [column]
  267. (let [id (:id column)
  268. property (db/entity id)
  269. internal-property {:db/ident (:id column)
  270. :block/original-name (:name column)}]
  271. (if (or property (timestamp-property? id))
  272. (set-property! (or property internal-property))
  273. (do
  274. (shui/popup-hide!)
  275. (let [property internal-property
  276. new-filter [property :text-contains]
  277. filters' (if (seq filters)
  278. (conj filters new-filter)
  279. [new-filter])]
  280. (set-filters! filters'))))))}
  281. option (cond
  282. timestamp?
  283. (merge option
  284. {:items timestamp-options
  285. :input-default-placeholder (if property (:block/original-name property) "Select")
  286. :on-chosen (fn [value]
  287. (shui/popup-hide!)
  288. (let [filters' (conj filters [property :after value])]
  289. (set-filters! filters')))})
  290. property
  291. (if (= :checkbox (get-in property [:block/schema :type]))
  292. (let [items [{:value true :label "true"}
  293. {:value false :label "false"}]]
  294. (merge option
  295. {:items items
  296. :input-default-placeholder (if property (:block/original-name property) "Select")
  297. :on-chosen (fn [value]
  298. (let [filters' (conj filters [property :is value])]
  299. (set-filters! filters')))}))
  300. (let [items (get-property-values (:data table) property)]
  301. (merge option
  302. {:items items
  303. :input-default-placeholder (if property (:block/original-name property) "Select")
  304. :multiple-choices? true
  305. :on-chosen (fn [_value _selected? selected]
  306. (let [filters' (if (seq selected)
  307. (conj filters [property :is selected])
  308. filters)]
  309. (set-filters! filters')))})))
  310. :else
  311. option)]
  312. (select/select option)))
  313. (rum/defc filter-properties < rum/static
  314. [columns table]
  315. (shui/button
  316. {:variant "ghost"
  317. :class "text-muted-foreground !px-1"
  318. :size :sm
  319. :on-click (fn [e]
  320. (shui/popup-show! (.-target e)
  321. (fn []
  322. (filter-property columns table))
  323. {:align :end
  324. :as-dropdown? true
  325. :auto-focus? true}))}
  326. (ui/icon "filter")))
  327. (defn operator->text
  328. [operator]
  329. (case operator
  330. :is "is"
  331. :is-not "is not"
  332. :text-contains "text contains"
  333. :text-not-contains "text not contains"
  334. :date-before "date before"
  335. :date-after "date after"
  336. :before "before"
  337. :after "after"
  338. :number-gt ">"
  339. :number-lt "<"
  340. :number-gte ">="
  341. :number-lte "<="
  342. :between "between"))
  343. (defn get-property-operators
  344. [property]
  345. (if (contains? #{:block/created-at :block/updated-at} (:db/ident property))
  346. [:before :after]
  347. (concat
  348. [:is :is-not]
  349. (case (get-in property [:block/schema :type])
  350. (:default :url :page :object)
  351. [:text-contains :text-not-contains]
  352. :date
  353. [:date-before :date-after]
  354. :number
  355. [:number-gt :number-lt :number-gte :number-lte :between]
  356. nil))))
  357. (defn- get-filter-with-changed-operator
  358. [property operator value]
  359. (case operator
  360. (:is :is-not)
  361. (when (set? value) value)
  362. (:text-contains :text-not-contains)
  363. (when (string? value) value)
  364. (:number-gt :number-lt :number-gte :number-lte)
  365. (when (number? value) value)
  366. :between
  367. (when (and (vector? value) (every? number? value))
  368. value)
  369. (:date-before :date-after :before :after)
  370. ;; FIXME: should be a valid date number
  371. (when (number? value) value)))
  372. (rum/defc filter-operator < rum/static
  373. [property operator filters set-filters! idx]
  374. (shui/dropdown-menu
  375. (shui/dropdown-menu-trigger
  376. {:asChild true}
  377. (shui/button
  378. {:class "!px-2 rounded-none border-r"
  379. :variant "ghost"
  380. :size :sm}
  381. [:span.text-xs (operator->text operator)]))
  382. (shui/dropdown-menu-content
  383. {:align "start"}
  384. (let [operators (get-property-operators property)]
  385. (for [operator operators]
  386. (shui/dropdown-menu-item
  387. {:on-click (fn []
  388. (let [new-filters (update filters idx
  389. (fn [[property _old-operator value]]
  390. (let [value' (get-filter-with-changed-operator property operator value)]
  391. (if value'
  392. [property operator value']
  393. [property operator]))))]
  394. (set-filters! new-filters)))}
  395. (operator->text operator)))))))
  396. (rum/defc between < rum/static
  397. [property [start end] filters set-filters! idx]
  398. [:<>
  399. (shui/input
  400. {:auto-focus true
  401. :placeholder "from"
  402. :value (str start)
  403. :onChange (fn [e]
  404. (let [input-value (util/evalue e)
  405. number-value (when-not (string/blank? input-value)
  406. (util/safe-parse-float input-value))
  407. value [number-value end]
  408. value (if (every? nil? value) nil value)]
  409. (let [new-filters (update filters idx
  410. (fn [[property operator _old_value]]
  411. (if (nil? value)
  412. [property operator]
  413. [property operator value])))]
  414. (set-filters! new-filters))))
  415. :class "w-24 !h-6 !py-0 border-none focus-visible:ring-0 focus-visible:ring-offset-0"})
  416. (shui/input
  417. {:value (str end)
  418. :placeholder "to"
  419. :onChange (fn [e]
  420. (let [input-value (util/evalue e)
  421. number-value (when-not (string/blank? input-value)
  422. (util/safe-parse-float input-value))
  423. value [start number-value]
  424. value (if (every? nil? value) nil value)]
  425. (let [new-filters (update filters idx
  426. (fn [[property operator _old_value]]
  427. (if (nil? value)
  428. [property operator]
  429. [property operator value])))]
  430. (set-filters! new-filters))))
  431. :class "w-24 !h-6 !py-0 border-none focus-visible:ring-0 focus-visible:ring-offset-0"})])
  432. (rum/defc filter-value-select < rum/static
  433. [{:keys [data-fns] :as table} property value operator idx]
  434. (let [type (get-in property [:block/schema :type])
  435. items (cond
  436. (contains? #{:before :after} operator)
  437. timestamp-options
  438. (= type :checkbox)
  439. [{:value true :label "true"} {:value false :label "false"}]
  440. :else
  441. (get-property-values (:data table) property))
  442. filters (get-in table [:state :filters])
  443. set-filters! (:set-filters! data-fns)
  444. many? (if (or (contains? #{:date-before :date-after :before :after} operator)
  445. (contains? #{:checkbox} type))
  446. false
  447. true)
  448. option (cond->
  449. {:input-default-placeholder (:block/original-name property)
  450. :input-opts {:class "!px-3 !py-1"}
  451. :items items
  452. :extract-fn :label
  453. :extract-chosen-fn :value
  454. :on-chosen (fn [value _selected? selected]
  455. (when-not many?
  456. (shui/popup-hide!))
  457. (let [value' (if many? selected value)
  458. new-filters (update filters idx
  459. (fn [[property operator _value]]
  460. [property operator value']))]
  461. (set-filters! new-filters)))}
  462. many?
  463. (assoc
  464. :multiple-choices? true
  465. :selected-choices value))]
  466. (shui/dropdown-menu
  467. (shui/dropdown-menu-trigger
  468. {:asChild true}
  469. (shui/button
  470. {:class "!px-2 rounded-none border-r"
  471. :variant "ghost"
  472. :size :sm}
  473. [:div.flex.flex-row.items-center.gap-1.text-xs
  474. (cond
  475. (de/entity? value)
  476. [:div (get-property-value-content value)]
  477. (string? value)
  478. [:div value]
  479. (boolean? value)
  480. [:div (str value)]
  481. (seq value)
  482. (->> (map (fn [v] [:div (get-property-value-content v)]) value)
  483. (interpose [:div "or"]))
  484. :else
  485. "Empty")]))
  486. (shui/dropdown-menu-content
  487. {:align "start"}
  488. (select/select option)))))
  489. (rum/defc filter-value < rum/static
  490. [table property operator value filters set-filters! idx]
  491. (let [number-operator? (string/starts-with? (name operator) "number-")]
  492. (case operator
  493. :between
  494. (between property value filters set-filters! idx)
  495. (:text-contains :text-not-contains :number-gt :number-lt :number-gte :number-lte)
  496. (shui/input
  497. {:auto-focus true
  498. :value (or value "")
  499. :onChange (fn [e]
  500. (let [value (util/evalue e)
  501. number-value (and number-operator? (when-not (string/blank? value)
  502. (util/safe-parse-float value)))]
  503. (let [new-filters (update filters idx
  504. (fn [[property operator _value]]
  505. (if (and number-operator? (nil? number-value))
  506. [property operator]
  507. [property operator (or number-value value)])))]
  508. (set-filters! new-filters))))
  509. :class "w-24 !h-6 !py-0 border-none focus-visible:ring-0 focus-visible:ring-offset-0"})
  510. (filter-value-select table property value operator idx))))
  511. (rum/defc filters-row < rum/static
  512. [{:keys [data-fns] :as table}]
  513. (let [filters (get-in table [:state :filters])
  514. {:keys [set-filters!]} data-fns]
  515. (when (seq filters)
  516. [:div.filters-row.flex.flex-row.items-center.gap-4.flex-wrap.pb-2
  517. (map-indexed
  518. (fn [idx filter]
  519. (let [[property operator value] filter]
  520. [:div.flex.flex-row.items-center.border.rounded
  521. (shui/button
  522. {:class "!px-2 rounded-none border-r"
  523. :variant "ghost"
  524. :size :sm
  525. :disabled true}
  526. [:span.text-xs (:block/original-name property)])
  527. (filter-operator property operator filters set-filters! idx)
  528. (filter-value table property operator value filters set-filters! idx)
  529. (shui/button
  530. {:class "!px-1 rounded-none"
  531. :variant "ghost"
  532. :size :sm
  533. :on-click (fn [_e]
  534. (let [new-filters (vec (remove #{filter} filters))]
  535. (set-filters! new-filters)))}
  536. (ui/icon "x"))]))
  537. filters)])))
  538. (defn- fuzzy-matched?
  539. [input s]
  540. (pos? (fuzzy-search/score (string/lower-case (str input))
  541. (string/lower-case (str s)))))
  542. (defn- row-matched?
  543. [row input filters]
  544. (and
  545. ;; full-text-search match
  546. (if (string/blank? input)
  547. true
  548. (when row
  549. (fuzzy-matched? input (:object/name row))))
  550. ;; filters check
  551. (every?
  552. (fn [[property operator match]]
  553. (let [property-ident (:db/ident property)
  554. value (get row property-ident)
  555. value' (cond
  556. (set? value) value
  557. (nil? value) #{}
  558. :else #{value})
  559. result
  560. (case operator
  561. :is
  562. (if (boolean? match)
  563. (= (boolean (get-property-value-content (get row property-ident))) match)
  564. (when (coll? value)
  565. (boolean (seq (set/intersection value' match)))))
  566. :is-not
  567. (if (boolean? match)
  568. (not= (boolean (get-property-value-content (get row property-ident))) match)
  569. (when (coll? value)
  570. (boolean (empty? (set/intersection value' match)))))
  571. :text-contains
  572. (some #(fuzzy-matched? match (get-property-value-content %)) value')
  573. :text-not-contains
  574. (not-any? #(string/includes? (str (get-property-value-content %)) match) value')
  575. :number-gt
  576. (if match (some #(> (get-property-value-content %) match) value') true)
  577. :number-gte
  578. (if match (some #(>= (get-property-value-content %) match) value') true)
  579. :number-lt
  580. (if match (some #(< (get-property-value-content %) match) value') true)
  581. :number-lte
  582. (if match (some #(<= (get-property-value-content %) match) value') true)
  583. :between
  584. (if (seq match)
  585. (some (fn [value-entity]
  586. (let [[start end] match
  587. value (get-property-value-content value-entity)
  588. conditions [(if start (<= start value) true)
  589. (if end (<= value end) true)]]
  590. (if (seq match) (every? true? conditions) true))) value')
  591. true)
  592. :date-before
  593. (if match (some #(< (:block/journal-day %) (:block/journal-day match)) value') true)
  594. :date-after
  595. (if match (some #(> (:block/journal-day %) (:block/journal-day match)) value') true)
  596. :before
  597. (let [search-value (get-timestamp match)]
  598. (if search-value (<= (get row property-ident) search-value) true))
  599. :after
  600. (let [search-value (get-timestamp match)]
  601. (if search-value (>= (get row property-ident) search-value) true))
  602. true)]
  603. result))
  604. filters)))
  605. (rum/defc objects-inner < rum/static
  606. [config class]
  607. (let [[input set-input!] (rum/use-state "")
  608. ;; TODO: block.temp/tagged-at
  609. [sorting set-sorting!] (rum/use-state [{:id :block/updated-at, :asc? false}])
  610. [filters set-filters!] (rum/use-state [])
  611. [row-filter set-row-filter!] (rum/use-state nil)
  612. [visible-columns set-visible-columns!] (rum/use-state {})
  613. [row-selection set-row-selection!] (rum/use-state {})
  614. [data set-data!] (rum/use-state (get-all-objects class))
  615. _ (rum/use-effect!
  616. (fn []
  617. ;; (when-let [^js worker @state/*db-worker]
  618. ;; (p/let [result-str (.get-page-refs-count worker (state/get-current-repo))
  619. ;; result (ldb/read-transit-str result-str)
  620. ;; data (map (fn [row] (assoc row :block.temp/refs-count (get result (:db/id row) 0))) data)]
  621. ;; (set-data! data)))
  622. )
  623. [])
  624. columns (build-columns class config)
  625. table (shui/table-option {:data data
  626. :columns columns
  627. :state {:sorting sorting
  628. :filters filters
  629. :row-filter row-filter
  630. :row-selection row-selection
  631. :visible-columns visible-columns}
  632. :data-fns {:set-filters! set-filters!
  633. :set-sorting! set-sorting!
  634. :set-visible-columns! set-visible-columns!
  635. :set-row-selection! set-row-selection!}})
  636. selected-rows (shui/table-get-selection-rows row-selection (:rows table))
  637. selected-rows-count (count selected-rows)
  638. selected? (pos? selected-rows-count)]
  639. (rum/use-effect!
  640. (fn []
  641. (set-row-filter! (fn []
  642. (fn [row]
  643. (row-matched? row input filters)))))
  644. [input filters])
  645. [:div.w-full.flex.flex-col.gap-2
  646. [:div.flex.items-center.justify-between
  647. [:div.flex.flex-row.items-center.gap-2
  648. [:div.font-medium (str (count data) " Objects")]]
  649. [:div.flex.items-center.gap-1
  650. (filter-properties columns table)
  651. (search input {:on-change set-input!})
  652. (more-actions columns table)]]
  653. (filters-row table)
  654. (let [columns' (:columns table)
  655. rows (:rows table)]
  656. [:div.rounded-md.border.content
  657. (ui/virtualized-table
  658. {:custom-scroll-parent (gdom/getElement "main-content-container")
  659. :total-count (count rows)
  660. :fixedHeaderContent (fn [] (table-header table columns'))
  661. :components {:Table (fn [props]
  662. (shui/table {}
  663. (.-children props)))
  664. :TableRow (fn [props] (table-row table rows columns' props))}})])
  665. (let [rows-count (count (:rows table))]
  666. [:div.flex.items-center.justify-end.space-x-2.py-4
  667. [:div.flex-1.text-sm.text-muted-foreground
  668. (if (pos? selected-rows-count)
  669. (str selected-rows-count " of " rows-count " row(s) selected.")
  670. (str "Total: " rows-count))]])]))
  671. (rum/defcs objects < mixins/container-id
  672. [state class]
  673. (objects-inner {:container-id (:container-id state)} class))