tldraw.cljs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. (ns frontend.extensions.tldraw
  2. "Adapters related to tldraw"
  3. (:require ["/frontend/tldraw-logseq" :as TldrawLogseq]
  4. [frontend.components.block :as block]
  5. [frontend.components.export :as export]
  6. [frontend.components.page :as page]
  7. [frontend.config :as config]
  8. [frontend.context.i18n :refer [t]]
  9. [frontend.db.model :as model]
  10. [frontend.handler.editor :as editor-handler]
  11. [frontend.handler.route :as route-handler]
  12. [frontend.handler.whiteboard :as whiteboard-handler]
  13. [frontend.handler.history :as history]
  14. [frontend.modules.shortcut.data-helper :as shortcut-helper]
  15. [frontend.rum :as r]
  16. [frontend.search :as search]
  17. [frontend.state :as state]
  18. [frontend.util :as util]
  19. [goog.object :as gobj]
  20. [promesa.core :as p]
  21. [rum.core :as rum]
  22. [frontend.ui :as ui]
  23. [frontend.components.whiteboard :as whiteboard]))
  24. (def tldraw (r/adapt-class (gobj/get TldrawLogseq "App")))
  25. (def generate-preview (gobj/get TldrawLogseq "generateJSXFromModel"))
  26. (rum/defc page-cp
  27. [props]
  28. (page/page {:page-name (model/get-redirect-page-name (gobj/get props "pageName")) :whiteboard? true}))
  29. (rum/defc block-cp
  30. [props]
  31. ((state/get-component :block/single-block) (uuid (gobj/get props "blockId"))))
  32. (rum/defc breadcrumb
  33. [props]
  34. (block/breadcrumb {:preview? true}
  35. (state/get-current-repo)
  36. (uuid (gobj/get props "blockId"))
  37. {:end-separator? (gobj/get props "endSeparator")
  38. :level-limit (gobj/get props "levelLimit" 3)}))
  39. (rum/defc tweet
  40. [props]
  41. (ui/tweet-embed (gobj/get props "tweetId")))
  42. (rum/defc block-reference
  43. [props]
  44. (block/block-reference {} (gobj/get props "blockId") nil))
  45. (rum/defc page-name-link
  46. [props]
  47. (block/page-cp {:preview? true} {:block/name (gobj/get props "pageName")}))
  48. (defn search-handler
  49. [q filters]
  50. (let [{:keys [pages? blocks? files?]} (js->clj filters {:keywordize-keys true})
  51. repo (state/get-current-repo)
  52. limit 100]
  53. (p/let [blocks (when blocks? (search/block-search repo q {:limit limit}))
  54. pages (when pages? (search/page-search q))
  55. files (when files? (search/file-search q limit))]
  56. (clj->js {:pages pages :blocks blocks :files files}))))
  57. (defn save-asset-handler
  58. [file]
  59. (-> (editor-handler/save-assets! nil (state/get-current-repo) [(js->clj file)])
  60. (p/then
  61. (fn [res]
  62. (when-let [[asset-file-name _ full-file-path] (and (seq res) (first res))]
  63. (editor-handler/resolve-relative-path (or full-file-path asset-file-name)))))))
  64. (defn references-count
  65. [props]
  66. (apply whiteboard/references-count
  67. (map (fn [k] (js->clj (gobj/get props k) {:keywordize-keys true})) ["id" "className" "options"])))
  68. (rum/defc keyboard-shortcut
  69. [props]
  70. (let [shortcut (shortcut-helper/gen-shortcut-seq (keyword (gobj/get props "action")))]
  71. (ui/render-keyboard-shortcut shortcut)))
  72. (def tldraw-renderers {:Page page-cp
  73. :Block block-cp
  74. :Breadcrumb breadcrumb
  75. :Tweet tweet
  76. :PageName page-name-link
  77. :BacklinksCount references-count
  78. :BlockReference block-reference
  79. :KeyboardShortcut keyboard-shortcut})
  80. (def undo (fn [] (history/undo! nil)))
  81. (def redo (fn [] (history/redo! nil)))
  82. (defn get-tldraw-handlers [current-whiteboard-name]
  83. {:t (fn [key] (t (keyword key)))
  84. :search search-handler
  85. :queryBlockByUUID (fn [block-uuid]
  86. (clj->js
  87. (model/query-block-by-uuid (parse-uuid block-uuid))))
  88. :getBlockPageName #(:block/name (model/get-block-page (state/get-current-repo) (parse-uuid %)))
  89. :exportToImage (fn [page-name options] (state/set-modal! #(export/export-blocks page-name (merge (js->clj options :keywordize-keys true) {:whiteboard? true}))))
  90. :isWhiteboardPage model/whiteboard-page?
  91. :isMobile util/mobile?
  92. :saveAsset save-asset-handler
  93. :makeAssetUrl editor-handler/make-asset-url
  94. :copyToClipboard (fn [text, html] (util/copy-to-clipboard! text :html html))
  95. :getRedirectPageName (fn [page-name-or-uuid] (model/get-redirect-page-name page-name-or-uuid))
  96. :insertFirstPageBlock (fn [page-name] (editor-handler/insert-first-page-block-if-not-exists! page-name {:redirect? false}))
  97. :addNewWhiteboard (fn [page-name]
  98. (whiteboard-handler/create-new-whiteboard-page! page-name))
  99. :addNewBlock (fn [content]
  100. (str (whiteboard-handler/add-new-block! current-whiteboard-name content)))
  101. :sidebarAddBlock (fn [uuid type]
  102. (state/sidebar-add-block! (state/get-current-repo)
  103. (:db/id (model/get-page uuid))
  104. (keyword type)))
  105. :redirectToPage (fn [page-name-or-uuid]
  106. (let [page-name (or (when (util/uuid-string? page-name-or-uuid)
  107. (:block/name (model/get-block-page (state/get-current-repo)
  108. (parse-uuid page-name-or-uuid))))
  109. page-name-or-uuid)
  110. page-exists? (model/page-exists? page-name)
  111. whiteboard? (model/whiteboard-page? page-name)]
  112. (when page-exists?
  113. (if whiteboard?
  114. (route-handler/redirect-to-whiteboard! page-name {:block-id page-name-or-uuid})
  115. (route-handler/redirect-to-page! (model/get-redirect-page-name page-name-or-uuid))))))})
  116. (rum/defc tldraw-app
  117. [page-name block-id]
  118. (let [populate-onboarding? (whiteboard-handler/should-populate-onboarding-whiteboard? page-name)
  119. data (whiteboard-handler/page-name->tldr! page-name)
  120. [loaded-app set-loaded-app] (rum/use-state nil)
  121. on-mount (fn [^js tln]
  122. (when tln
  123. (set! (.-appUndo tln) undo)
  124. (set! (.-appRedo tln) redo)
  125. (when-let [^js api (gobj/get tln "api")]
  126. (p/then (when populate-onboarding?
  127. (whiteboard-handler/populate-onboarding-whiteboard api))
  128. #(do (whiteboard-handler/cleanup! (.-currentPage tln))
  129. (state/focus-whiteboard-shape tln block-id)
  130. (set-loaded-app tln))))))]
  131. (rum/use-effect! (fn []
  132. (when (and loaded-app block-id)
  133. (state/focus-whiteboard-shape loaded-app block-id))
  134. #())
  135. [block-id loaded-app])
  136. (when data
  137. [:div.draw.tldraw.whiteboard.relative.w-full.h-full
  138. {:style {:overscroll-behavior "none"}
  139. :on-blur (fn [e]
  140. (when (#{"INPUT" "TEXTAREA"} (.-tagName (gobj/get e "target")))
  141. (state/clear-edit!)))
  142. ;; wheel -> overscroll may cause browser navigation
  143. :on-wheel util/stop-propagation}
  144. (when
  145. (and populate-onboarding? (not loaded-app))
  146. [:div.absolute.inset-0.flex.items-center.justify-center
  147. {:style {:z-index 200}}
  148. (ui/loading "Loading onboarding whiteboard ...")])
  149. (tldraw {:renderers tldraw-renderers
  150. :handlers (get-tldraw-handlers page-name)
  151. :onMount on-mount
  152. :readOnly config/publishing?
  153. :onPersist (fn [app info]
  154. (state/set-state! [:whiteboard/last-persisted-at (state/get-current-repo)] (util/time-ms))
  155. (util/profile "tldraw persist"
  156. (whiteboard-handler/transact-tldr-delta! page-name app (.-replace info))))
  157. :model data})])))