assets.cljs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. (ns frontend.extensions.pdf.assets
  2. (:require [cljs.reader :as reader]
  3. [clojure.string :as string]
  4. [frontend.config :as config]
  5. [frontend.db.model :as db-model]
  6. [frontend.db.utils :as db-utils]
  7. [frontend.fs :as fs]
  8. [frontend.handler.editor :as editor-handler]
  9. [frontend.handler.page :as page-handler]
  10. [frontend.handler.assets :as assets-handler]
  11. [frontend.handler.notification :as notification]
  12. [frontend.ui :as ui]
  13. [frontend.context.i18n :refer [t]]
  14. [frontend.extensions.lightbox :as lightbox]
  15. [frontend.util.page-property :as page-property]
  16. [frontend.state :as state]
  17. [frontend.util :as util]
  18. [frontend.extensions.pdf.utils :as pdf-utils]
  19. [logseq.graph-parser.config :as gp-config]
  20. [logseq.graph-parser.util.block-ref :as block-ref]
  21. [medley.core :as medley]
  22. [promesa.core :as p]
  23. [reitit.frontend.easy :as rfe]
  24. [rum.core :as rum]))
  25. (defn hls-file?
  26. [filename]
  27. (and filename (string? filename) (string/starts-with? filename "hls__")))
  28. (defn inflate-asset
  29. [original-path]
  30. (let [filename (util/node-path.basename original-path)
  31. web-link? (string/starts-with? original-path "http")
  32. ext-name (util/get-file-ext filename)
  33. url (assets-handler/normalize-asset-resource-url original-path)]
  34. (when-let [key
  35. (if web-link?
  36. (str (hash url))
  37. (and
  38. (= ext-name "pdf")
  39. (subs filename 0 (- (count filename) 4))))]
  40. {:key key
  41. :identity (subs key (- (count key) 15))
  42. :filename filename
  43. :url url
  44. :hls-file (str "assets/" key ".edn")
  45. :original-path original-path})))
  46. (defn resolve-area-image-file
  47. [img-stamp current {:keys [page id] :as _hl}]
  48. (when-let [key (:key current)]
  49. (-> (str gp-config/local-assets-dir "/" key "/")
  50. (str (util/format "%s_%s_%s.png" page id img-stamp)))))
  51. (defn load-hls-data$
  52. [{:keys [hls-file]}]
  53. (when hls-file
  54. (let [repo-cur (state/get-current-repo)
  55. repo-dir (config/get-repo-dir repo-cur)]
  56. (p/let [_ (fs/create-if-not-exists repo-cur repo-dir hls-file "{:highlights []}")
  57. res (fs/read-file repo-dir hls-file)
  58. data (if res (reader/read-string res) {})]
  59. data))))
  60. (defn persist-hls-data$
  61. [{:keys [hls-file]} highlights extra]
  62. (when hls-file
  63. (let [repo-cur (state/get-current-repo)
  64. repo-dir (config/get-repo-dir repo-cur)
  65. data (pr-str {:highlights highlights :extra extra})]
  66. (fs/write-file! repo-cur repo-dir hls-file data {:skip-compare? true}))))
  67. (defn resolve-hls-data-by-key$
  68. [target-key]
  69. ;; TODO: fuzzy match
  70. (when-let [hls-file (and target-key (str gp-config/local-assets-dir "/" target-key ".edn"))]
  71. (load-hls-data$ {:hls-file hls-file})))
  72. (defn area-highlight?
  73. [hl]
  74. (and hl (not (nil? (get-in hl [:content :image])))))
  75. (defn persist-hl-area-image$
  76. [^js viewer current new-hl old-hl {:keys [top left width height]}]
  77. (when-let [^js canvas (and (:key current) (.-canvas (.getPageView viewer (dec (:page new-hl)))))]
  78. (let [^js doc (.-ownerDocument canvas)
  79. ^js canvas' (.createElement doc "canvas")
  80. dpr js/window.devicePixelRatio
  81. repo-cur (state/get-current-repo)
  82. repo-dir (config/get-repo-dir repo-cur)
  83. dw (* dpr width)
  84. dh (* dpr height)]
  85. (set! (. canvas' -width) dw)
  86. (set! (. canvas' -height) dh)
  87. (when-let [^js ctx (.getContext canvas' "2d" #js{:alpha false})]
  88. (set! (. ctx -imageSmoothingEnabled) false)
  89. (.drawImage
  90. ctx canvas
  91. (* left dpr) (* top dpr) (* width dpr) (* height dpr)
  92. 0 0 dw dh)
  93. (let [callback (fn [^js png]
  94. ;; write image file
  95. (p/catch
  96. (p/let [_ (js/console.time :write-area-image)
  97. ^js png (.arrayBuffer png)
  98. {:keys [key]} current
  99. ;; dir
  100. fstamp (get-in new-hl [:content :image])
  101. old-fstamp (and old-hl (get-in old-hl [:content :image]))
  102. fname (str (:page new-hl) "_" (:id new-hl))
  103. fdir (str gp-config/local-assets-dir "/" key)
  104. _ (fs/mkdir-if-not-exists (str repo-dir "/" fdir))
  105. new-fpath (str fdir "/" fname "_" fstamp ".png")
  106. old-fpath (and old-fstamp (str fdir "/" fname "_" old-fstamp ".png"))
  107. _ (and old-fpath (apply fs/rename! repo-cur (map #(util/node-path.join repo-dir %) [old-fpath new-fpath])))
  108. _ (fs/write-file! repo-cur repo-dir new-fpath png {:skip-compare? true})]
  109. (js/console.timeEnd :write-area-image))
  110. (fn [err]
  111. (js/console.error "[write area image Error]" err))))]
  112. (.toBlob canvas' callback))
  113. ))))
  114. (defn update-hl-block!
  115. [highlight]
  116. (when-let [block (db-model/get-block-by-uuid (:id highlight))]
  117. (doseq [[k v] {:hl-stamp (if (area-highlight? highlight)
  118. (get-in highlight [:content :image])
  119. (js/Date.now))
  120. :hl-color (get-in highlight [:properties :color])}]
  121. (editor-handler/set-block-property! (:block/uuid block) k v))))
  122. (defn unlink-hl-area-image$
  123. [^js _viewer current hl]
  124. (when-let [fkey (and (area-highlight? hl) (:key current))]
  125. (let [repo-cur (state/get-current-repo)
  126. repo-dir (config/get-repo-dir repo-cur)
  127. fstamp (get-in hl [:content :image])
  128. fname (str (:page hl) "_" (:id hl))
  129. fdir (str gp-config/local-assets-dir "/" fkey)
  130. fpath (util/node-path.join repo-dir (str fdir "/" fname "_" fstamp ".png"))]
  131. (fs/unlink! repo-cur fpath {}))))
  132. (defn resolve-ref-page
  133. [pdf-current]
  134. (let [page-name (:key pdf-current)
  135. page-name (string/trim page-name)
  136. page-name (str "hls__" page-name)
  137. page (db-model/get-page page-name)
  138. file-path (:original-path pdf-current)
  139. format (state/get-preferred-format)
  140. repo-dir (config/get-repo-dir (state/get-current-repo))
  141. asset-dir (util/node-path.join repo-dir gp-config/local-assets-dir)
  142. url (if (string/includes? file-path asset-dir)
  143. (str ".." (last (string/split file-path repo-dir)))
  144. file-path)]
  145. (if-not page
  146. (let [label (:filename pdf-current)]
  147. (page-handler/create! page-name {:redirect? false :create-first-block? false
  148. :split-namespace? false
  149. :format format
  150. :properties {:file (case format
  151. :markdown
  152. (util/format "[%s](%s)" label url)
  153. :org
  154. (util/format "[[%s][%s]]" url label)
  155. url)
  156. :file-path url}})
  157. (db-model/get-page page-name))
  158. ;; try to update file path
  159. (page-property/add-property! page-name :file-path url))
  160. page))
  161. (defn ensure-ref-block!
  162. ([pdf hl] (ensure-ref-block! pdf hl nil))
  163. ([pdf-current {:keys [id content page properties]} insert-opts]
  164. (when-let [ref-page (and pdf-current (resolve-ref-page pdf-current))]
  165. (if-let [ref-block (db-model/query-block-by-uuid id)]
  166. (do
  167. (println "[existed ref block]" ref-block)
  168. ref-block)
  169. (let [text (:text content)
  170. wrap-props #(if-let [stamp (:image content)]
  171. (assoc % :hl-type "area" :hl-stamp stamp) %)]
  172. (when (string? text)
  173. (editor-handler/api-insert-new-block!
  174. text (merge {:page (:block/name ref-page)
  175. :custom-uuid id
  176. :properties (wrap-props
  177. {:ls-type "annotation"
  178. :hl-page page
  179. :hl-color (:color properties)
  180. ;; force custom uuid
  181. :id (str id)})}
  182. insert-opts))))))))
  183. (defn del-ref-block!
  184. [{:keys [id]}]
  185. #_:clj-kondo/ignore
  186. (when-let [repo (state/get-current-repo)]
  187. (when-let [block (db-model/get-block-by-uuid id)]
  188. (editor-handler/delete-block-aux! block true))))
  189. (defn copy-hl-ref!
  190. [highlight]
  191. (when-let [ref-block (ensure-ref-block! (state/get-current-pdf) highlight)]
  192. (util/copy-to-clipboard! (block-ref/->block-ref (:block/uuid ref-block)))))
  193. (defn open-block-ref!
  194. [block]
  195. (let [id (:block/uuid block)
  196. page (db-utils/pull (:db/id (:block/page block)))
  197. page-name (:block/original-name page)
  198. file-path (:file-path (:block/properties page))]
  199. (when-let [target-key (and page-name (subs page-name 5))]
  200. (p/let [hls (resolve-hls-data-by-key$ target-key)
  201. hls (and hls (:highlights hls))]
  202. (let [file-path (or file-path (str "../assets/" target-key ".pdf"))]
  203. (if-let [matched (and hls (medley/find-first #(= id (:id %)) hls))]
  204. (do
  205. (state/set-state! :pdf/ref-highlight matched)
  206. ;; open pdf viewer
  207. (state/set-current-pdf! (inflate-asset file-path)))
  208. (js/console.debug "[Unmatched highlight ref]" block)))))))
  209. (defn goto-block-ref!
  210. [{:keys [id] :as hl}]
  211. (when id
  212. (ensure-ref-block!
  213. (state/get-current-pdf) hl {:edit-block? false})
  214. (rfe/push-state :page {:name (str id)})))
  215. (defn goto-annotations-page!
  216. ([current] (goto-annotations-page! current nil))
  217. ([current id]
  218. (when-let [name (:key current)]
  219. (rfe/push-state :page {:name (str "hls__" name)} (if id {:anchor (str "block-content-" + id)} nil)))))
  220. (defn open-lightbox
  221. [e]
  222. (let [images (js/document.querySelectorAll ".hl-area img")
  223. images (to-array images)
  224. images (if-not (= (count images) 1)
  225. (let [^js image (.closest (.-target e) ".hl-area")
  226. image (. image querySelector "img")]
  227. (->> images
  228. (sort-by (juxt #(.-y %) #(.-x %)))
  229. (split-with (complement #{image}))
  230. reverse
  231. (apply concat)))
  232. images)
  233. images (for [^js it images] {:src (.-src it)
  234. :w (.-naturalWidth it)
  235. :h (.-naturalHeight it)})]
  236. (when (seq images)
  237. (lightbox/preview-images! images))))
  238. (rum/defc area-display
  239. [block]
  240. (when-let [asset-path' (and block (pdf-utils/get-area-block-asset-url
  241. block (db-utils/pull (:db/id (:block/page block)))))]
  242. (let [asset-path (editor-handler/make-asset-url asset-path')]
  243. [:span.hl-area
  244. [:span.actions
  245. (when-not config/publishing?
  246. [:button.asset-action-btn.px-1
  247. {:title (t :asset/copy)
  248. :tabIndex "-1"
  249. :on-mouse-down util/stop
  250. :on-click (fn [e]
  251. (util/stop e)
  252. (-> (util/copy-image-to-clipboard (gp-config/remove-asset-protocol asset-path))
  253. (p/then #(notification/show! "Copied!" :success))))}
  254. (ui/icon "copy")])
  255. [:button.asset-action-btn.px-1
  256. {:title (t :asset/maximize)
  257. :tabIndex "-1"
  258. :on-mouse-down util/stop
  259. :on-click open-lightbox}
  260. (ui/icon "maximize")]]
  261. [:img {:src asset-path}]])))
  262. (defn human-page-name
  263. [page-name]
  264. (cond
  265. (string/starts-with? page-name "hls__")
  266. (pdf-utils/fix-local-asset-pagename page-name)
  267. :else (util/trim-safe page-name)))