whiteboard.cljs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. (ns frontend.components.whiteboard
  2. (:require [cljs.math :as math]
  3. [datascript.core :as d]
  4. [frontend.components.page :as page]
  5. [frontend.components.reference :as reference]
  6. [frontend.context.i18n :refer [t]]
  7. [frontend.db.model :as model]
  8. [frontend.handler.route :as route-handler]
  9. [frontend.handler.whiteboard :as whiteboard-handler]
  10. [frontend.state :as state]
  11. [frontend.ui :as ui]
  12. [frontend.util :as util]
  13. [promesa.core :as p]
  14. [rum.core :as rum]
  15. [shadow.loader :as loader]))
  16. (defonce tldraw-loaded? (atom false))
  17. (rum/defc tldraw-app < rum/reactive
  18. {:init (fn [state]
  19. (p/let [_ (loader/load :tldraw)]
  20. (reset! tldraw-loaded? true))
  21. state)}
  22. [name shape-id]
  23. (let [loaded? (rum/react tldraw-loaded?)
  24. draw-component (when loaded?
  25. (resolve 'frontend.extensions.tldraw/tldraw-app))]
  26. (when draw-component
  27. (draw-component name shape-id))))
  28. ;; TODO: make it reactive to db changes
  29. (rum/defc tldraw-preview < rum/reactive
  30. {:init (fn [state]
  31. (p/let [_ (loader/load :tldraw)]
  32. (reset! tldraw-loaded? true))
  33. state)}
  34. [page-name]
  35. (let [loaded? (rum/react tldraw-loaded?)
  36. tldr (whiteboard-handler/page-name->tldr! page-name)
  37. generate-preview (when loaded?
  38. (resolve 'frontend.extensions.tldraw/generate-preview))]
  39. (when generate-preview
  40. (generate-preview tldr))))
  41. (rum/defc page-refs-count < rum/static
  42. ([page-name classname]
  43. (page-refs-count page-name classname nil))
  44. ([page-name classname render-fn]
  45. (let [page-entity (model/get-page page-name)
  46. block-uuid (:block/uuid page-entity)
  47. ref (rum/use-ref nil)
  48. refs-count (count (:block/_refs page-entity))
  49. [open-flag set-open-flag] (rum/use-state 0)
  50. open? (not= open-flag 0)
  51. d-open-flag (rum/use-memo #(util/debounce 200 set-open-flag) [])]
  52. ;; TODO: move click outside to the utility?
  53. (rum/use-effect!
  54. (let [listener (fn [e]
  55. (when (and (.-current ref)
  56. (not (.contains (.-current ref) (.-target e))))
  57. (d-open-flag 0)))]
  58. (.addEventListener js/document.body "mousedown" listener true)
  59. #(.removeEventListener js/document.body "mousedown" listener))
  60. [ref])
  61. (when (> refs-count 0)
  62. (ui/tippy {:in-editor? false
  63. :html (fn [] [:div.mx-2 {:ref ref} (reference/block-linked-references block-uuid)])
  64. :interactive true
  65. :position "bottom"
  66. :distance 10
  67. :open? open?
  68. :popperOptions {:modifiers {:preventOverflow
  69. {:enabled true
  70. :boundariesElement "viewport"}}}}
  71. [:div.flex.items-center.gap-2.whiteboard-page-refs-count
  72. {:class (str classname (when open? " open"))
  73. :on-mouse-enter (fn [] (d-open-flag #(if (= % 0) 1 %)))
  74. :on-mouse-leave (fn [] (d-open-flag #(if (= % 2) % 0)))
  75. :on-click (fn [e]
  76. (util/stop e)
  77. (d-open-flag (fn [o] (if (not= o 2) 2 0))))}
  78. [:div.open-page-ref-link refs-count]
  79. (when render-fn (render-fn open?))])))))
  80. (defn- get-page-display-name
  81. [page-name]
  82. (let [page-entity (model/get-page page-name)]
  83. (or
  84. (get-in page-entity [:block/properties :title] nil)
  85. (:block/original-name page-entity)
  86. page-name)))
  87. ;; This is not accurate yet
  88. (defn- get-page-human-update-time
  89. [page-name]
  90. (let [page-entity (model/get-page page-name)
  91. {:block/keys [updated-at created-at]} page-entity]
  92. (str (if (= created-at updated-at) "Created " "Edited ")
  93. (util/time-ago (js/Date. updated-at)))))
  94. (rum/defc dashboard-preview-card
  95. [page-name]
  96. [:div.dashboard-card.dashboard-preview-card.cursor-pointer.hover:shadow-lg
  97. {:on-click
  98. (fn [e]
  99. (util/stop e)
  100. (route-handler/redirect-to-whiteboard! page-name))}
  101. [:div.dashboard-card-title
  102. [:div.flex.w-full
  103. [:div.dashboard-card-title-name.font-bold
  104. (if (parse-uuid page-name)
  105. [:span.opacity-50 (t :untitled)]
  106. (get-page-display-name page-name))]
  107. [:div.flex-1]]
  108. [:div.flex.w-full.opacity-50
  109. [:div (get-page-human-update-time page-name)]
  110. [:div.flex-1]
  111. (page-refs-count page-name nil)]]
  112. [:div.p-4.h-64.flex.justify-center
  113. (tldraw-preview page-name)]])
  114. (rum/defc dashboard-create-card
  115. []
  116. [:div.dashboard-card.dashboard-create-card.cursor-pointer
  117. {:on-click
  118. (fn [e]
  119. (util/stop e)
  120. (let [name (str (d/squuid))]
  121. (whiteboard-handler/create-new-whiteboard-page! name)
  122. (route-handler/redirect-to-whiteboard! name)))}
  123. (ui/icon "plus")
  124. [:span.dashboard-create-card-caption.select-none
  125. "New whiteboard"]])
  126. ;; TODO: move it to util?
  127. (defn- use-component-size
  128. [ref]
  129. (let [[rect set-rect] (rum/use-state nil)]
  130. (rum/use-effect!
  131. (fn []
  132. (let [update-rect #(set-rect (when (.-current ref) (.. ref -current getBoundingClientRect)))
  133. updator (fn [entries]
  134. (when (.-contentRect (first (js->clj entries))) (update-rect)))
  135. observer (js/ResizeObserver. updator)]
  136. (update-rect)
  137. (.observe observer (.-current ref))
  138. #(.disconnect observer)))
  139. [])
  140. rect))
  141. (rum/defc whiteboard-dashboard
  142. []
  143. (let [whiteboards (->> (model/get-all-whiteboards (state/get-current-repo))
  144. (sort-by :block/updated-at)
  145. reverse)
  146. whiteboard-names (map :block/name whiteboards)
  147. ref (rum/use-ref nil)
  148. rect (use-component-size ref)
  149. [container-width] (when rect [(.-width rect) (.-height rect)])
  150. cols (cond (< container-width 600) 1
  151. (< container-width 900) 2
  152. (< container-width 1200) 3
  153. :else 4)
  154. total-whiteboards (count whiteboards)
  155. empty-cards (- (max (* (math/ceil (/ (inc total-whiteboards) cols)) cols) (* 2 cols))
  156. (inc total-whiteboards))]
  157. [:<>
  158. [:h1.title.select-none
  159. "All whiteboards"
  160. [:span.opacity-50
  161. (str " · " total-whiteboards)]]
  162. [:div
  163. {:ref ref}
  164. [:div.gap-8.grid.grid-rows-auto
  165. {:style {:grid-template-columns (str "repeat(" cols ", minmax(0, 1fr))")}}
  166. (dashboard-create-card)
  167. (for [whiteboard-name whiteboard-names]
  168. [:<> {:key whiteboard-name} (dashboard-preview-card whiteboard-name)])
  169. (for [n (range empty-cards)]
  170. [:div.dashboard-card.dashboard-bg-card {:key n}])]]]))
  171. (rum/defc whiteboard-page
  172. [name block-id]
  173. [:div.absolute.w-full.h-full.whiteboard-page
  174. ;; makes sure the whiteboard will not cover the borders
  175. {:key name
  176. :style {:padding "0.5px" :z-index 0
  177. :transform "translateZ(0)"
  178. :text-rendering "geometricPrecision"
  179. :-webkit-font-smoothing "subpixel-antialiased"}}
  180. [:div.whiteboard-page-title-root
  181. [:span.whiteboard-page-title
  182. {:style {:color "var(--ls-primary-text-color)"
  183. :user-select "none"}}
  184. (page/page-title name
  185. [:span.tie.tie-whiteboard
  186. {:style {:font-size "0.9em"}}]
  187. (get-page-display-name name)
  188. nil
  189. false)]
  190. (page-refs-count name
  191. "text-md px-3 py-2 cursor-default whiteboard-page-refs-count"
  192. (fn [open?] [:<> "Reference" (ui/icon (if open? "references-hide" "references-show"))]))]
  193. (tldraw-app name block-id)])
  194. (rum/defc whiteboard-route
  195. [route-match]
  196. (let [name (get-in route-match [:parameters :path :name])
  197. {:keys [block-id]} (get-in route-match [:parameters :query])]
  198. (whiteboard-page name block-id)))