page.cljs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. (ns frontend.components.db-based.page
  2. "Page components only for DB graphs"
  3. (:require [frontend.components.block :as component-block]
  4. [frontend.components.editor :as editor]
  5. [frontend.components.class :as class-component]
  6. [frontend.components.property :as property-component]
  7. [frontend.components.property.value :as pv]
  8. [frontend.components.icon :as icon-component]
  9. [frontend.config :as config]
  10. [frontend.db :as db]
  11. [frontend.handler.db-based.property :as db-property-handler]
  12. [frontend.handler.property.util :as pu]
  13. [frontend.handler.db-based.property.util :as db-pu]
  14. [frontend.ui :as ui]
  15. [frontend.state :as state]
  16. [rum.core :as rum]
  17. [logseq.shui.ui :as shui-ui]
  18. [frontend.util :as util]
  19. [clojure.set :as set]
  20. [clojure.string :as string]))
  21. (rum/defc page-properties < rum/reactive
  22. [page {:keys [configure? mode]}]
  23. (let [types (:block/type page)
  24. class? (contains? types "class")
  25. property? (contains? types "property")
  26. edit-input-id-prefix (str "edit-block-" (:block/uuid page))
  27. configure-opts {:selected? false
  28. :page-configure? true}
  29. has-viewable-properties? (db-property-handler/block-has-viewable-properties? page)
  30. has-class-properties? (seq (:properties (:block/schema page)))]
  31. (when (or configure? has-viewable-properties? has-class-properties? property?)
  32. [:div.ls-page-properties.mb-4
  33. (if configure?
  34. (cond
  35. (and class? has-class-properties? (= :class mode))
  36. nil
  37. (and class? (not has-class-properties?))
  38. (component-block/db-properties-cp {:editor-box editor/box}
  39. page
  40. (str edit-input-id-prefix "-schema")
  41. (assoc configure-opts :class-schema? true))
  42. (not (db-property-handler/block-has-viewable-properties? page))
  43. (component-block/db-properties-cp {:editor-box editor/box}
  44. page
  45. (str edit-input-id-prefix "-page")
  46. (assoc configure-opts :class-schema? false)))
  47. (if config/publishing?
  48. [:div.flex.flex-col.gap-4
  49. (when has-viewable-properties?
  50. [:div
  51. (when has-class-properties?
  52. [:div.mb-1.opacity-70.font-medium.text-sm "Page properties:"])
  53. (component-block/db-properties-cp {:editor-box editor/box}
  54. page
  55. (str edit-input-id-prefix "-page")
  56. {:selected? false
  57. :page-configure? false
  58. :class-schema? false})])
  59. (when has-class-properties?
  60. [:div
  61. (when has-viewable-properties?
  62. [:div.mb-1.opacity-70.font-medium.text-sm "Class properties:"])
  63. (component-block/db-properties-cp {:editor-box editor/box}
  64. page
  65. (str edit-input-id-prefix "-schema")
  66. (assoc configure-opts :class-schema? true))])]
  67. [:div.flex.flex-col.gap-4
  68. (when has-class-properties?
  69. [:div
  70. (when has-viewable-properties?
  71. [:div.mb-1.opacity-70.font-medium.text-sm "Class properties:"])
  72. (component-block/db-properties-cp {:editor-box editor/box}
  73. page
  74. (str edit-input-id-prefix "-schema")
  75. (assoc configure-opts :class-schema? true))])
  76. (when has-viewable-properties?
  77. [:div
  78. (when has-class-properties?
  79. [:div.mb-1.opacity-70.font-medium.text-sm "Page properties:"])
  80. (component-block/db-properties-cp {:editor-box editor/box}
  81. page
  82. (str edit-input-id-prefix "-page")
  83. {:selected? false
  84. :page-configure? false
  85. :class-schema? false})])]))])))
  86. (rum/defc icon-row < rum/reactive
  87. [page]
  88. [:div.grid.grid-cols-5.gap-1.items-center
  89. [:label.col-span-2 "Icon:"]
  90. (let [icon-value (pu/get-block-property-value page :icon)]
  91. [:div.col-span-3.flex.flex-row.items-center.gap-2
  92. (icon-component/icon-picker icon-value
  93. {:disabled? config/publishing?
  94. :on-chosen (fn [_e icon]
  95. (let [icon-property-id (db-pu/get-built-in-property-uuid :icon)]
  96. (db-property-handler/update-property!
  97. (state/get-current-repo)
  98. (:block/uuid page)
  99. {:properties {icon-property-id icon}})))})
  100. (when (and icon-value (not config/publishing?))
  101. [:a.fade-link.flex {:on-click (fn [_e]
  102. (db-property-handler/remove-block-property!
  103. (state/get-current-repo)
  104. (:block/uuid page)
  105. (db-pu/get-built-in-property-uuid :icon)))
  106. :title "Delete this icon"}
  107. (ui/icon "X")])])])
  108. (rum/defc tags
  109. [page]
  110. (let [tags-property (pu/get-property :tags)]
  111. (pv/property-value page tags-property
  112. (map :block/uuid (:block/tags page))
  113. {:page-cp (fn [config page]
  114. (component-block/page-cp (assoc config :tag? true) page))
  115. :inline-text component-block/inline-text})))
  116. (rum/defc tags-row < rum/reactive
  117. [page]
  118. [:div.grid.grid-cols-5.gap-1.items-center
  119. [:label.col-span-2 "Tags:"]
  120. [:div.col-span-3.flex.flex-row.items-center.gap-2
  121. (tags page)]])
  122. (rum/defcs page-configure < rum/reactive
  123. [state page *mode]
  124. (let [*mode *mode
  125. mode (rum/react *mode)
  126. types (:block/type page)
  127. class? (contains? types "class")
  128. property? (contains? types "property")
  129. page-opts {:configure? true}]
  130. (when (nil? mode)
  131. (reset! *mode (cond
  132. property? :property
  133. class? :class
  134. :else :page)))
  135. [:div.flex.flex-col.gap-1
  136. (if (= mode :property)
  137. (property-component/property-config page page {:inline-text component-block/inline-text})
  138. [:<>
  139. (when (= mode :class)
  140. (class-component/configure page {:show-title? false}))
  141. (when-not config/publishing? (tags-row page))
  142. (when-not config/publishing? (icon-row page))
  143. (page-properties page (assoc page-opts :mode mode))])]))
  144. (rum/defc page-properties-react < rum/reactive
  145. [page* page-opts]
  146. (let [page (db/sub-block (:db/id page*))]
  147. (when (or (db-property-handler/block-has-viewable-properties? page)
  148. ;; Allow class and property pages to add new property
  149. (some #{"class" "property"} (:block/type page)))
  150. (page-properties page page-opts))))
  151. (rum/defc mode-switch < rum/reactive
  152. [types *mode]
  153. (let [current-mode (rum/react *mode)
  154. property? (contains? types "property")
  155. class? (contains? types "class")
  156. modes (->
  157. (cond
  158. (and property? class?)
  159. ["Property" "Class"]
  160. property?
  161. ["Property"]
  162. class?
  163. ["Class"]
  164. :else
  165. [])
  166. (conj "Page"))]
  167. [:div.flex.flex-row.items-center.gap-1
  168. (for [mode modes]
  169. (let [mode' (keyword (string/lower-case mode))
  170. selected? (= mode' current-mode)]
  171. (shui-ui/button {:variant (if selected? :outline :ghost) :size :sm
  172. :on-click (if config/publishing?
  173. util/stop-propagation
  174. (fn [e]
  175. (util/stop-propagation e)
  176. (reset! *mode mode')))}
  177. mode)))]))
  178. (rum/defcs page-info < rum/reactive
  179. (rum/local false ::hover?)
  180. (rum/local nil ::mode)
  181. {:init (if config/publishing?
  182. (fn [state]
  183. (let [page* (first (:rum/args state))
  184. page (db/sub-block (:db/id page*))]
  185. (assoc state
  186. ::collapsed?
  187. (atom (not (seq (set/intersection #{"class" "property"} (:block/type page))))))))
  188. (fn [state]
  189. (assoc state ::collapsed? (atom true))))}
  190. [state page *hover-title?]
  191. (let [page (db/sub-block (:db/id page))
  192. *collapsed? (::collapsed? state)
  193. *hover? (::hover? state)
  194. *mode (::mode state)
  195. types (:block/type page)
  196. hover-title? (rum/react *hover-title?)
  197. collapsed? (rum/react *collapsed?)
  198. has-tags? (seq (:block/tags page))
  199. hover-or-expanded? (or @*hover? hover-title? (not collapsed?))]
  200. (prn :debug :collapsed? collapsed?)
  201. (when (if config/publishing?
  202. ;; Since publishing is read-only, hide this component if it has no info to show
  203. ;; as it creates a fair amount of empty vertical space
  204. (or has-tags? (some? types))
  205. true)
  206. [:div.page-info {:on-mouse-over #(reset! *hover? true)
  207. :on-mouse-leave #(reset! *hover? false)}
  208. (when (or hover-or-expanded? has-tags?)
  209. [:div.fade-in.p-2 (cond-> {}
  210. (or @*hover? (not collapsed?))
  211. (assoc :class "border rounded"))
  212. [:div.info-title.cursor {:on-click
  213. (if config/publishing?
  214. (fn [_]
  215. (when (seq (set/intersection #{"class" "empty"} types))
  216. (swap! *collapsed? not)))
  217. #(swap! *collapsed? not))}
  218. [:div.flex.flex-row.items-center.gap-2.justify-between
  219. [:div.flex.flex-row.items-center.gap-2
  220. (if collapsed?
  221. [:<>
  222. (shui-ui/button {:variant :ghost :size :sm :class "fade-link"}
  223. (ui/icon "tags"))
  224. [:div {:on-click util/stop-propagation}
  225. (tags page)]]
  226. [:div.flex.flex-row.items-center.gap-1
  227. (shui-ui/button {:variant :ghost :size :sm :class "fade-link"}
  228. (ui/icon "info-circle"))
  229. [:a.text-sm.font-medium.fade-link
  230. "Configure:"]
  231. (mode-switch types *mode)])]
  232. (when (or @*hover? (not collapsed?))
  233. (shui-ui/button
  234. {:variant :ghost :size :sm :class "fade-link"}
  235. (ui/icon (if collapsed?
  236. "chevron-down"
  237. "chevron-up"))))]]
  238. (when-not collapsed?
  239. [:div.py-2.px-4
  240. (page-configure page *mode)])])])))