highlights.cljs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. (ns frontend.extensions.pdf.highlights
  2. (:require [rum.core :as rum]
  3. [promesa.core :as p]
  4. [cljs-bean.core :as bean]
  5. [medley.core :as medley]
  6. [frontend.context.i18n :as i18n]
  7. [frontend.handler.notification :as notification]
  8. [frontend.extensions.pdf.utils :as pdf-utils]
  9. [frontend.extensions.pdf.assets :as pdf-assets]
  10. [frontend.util :as front-utils]
  11. [frontend.state :as state]
  12. [frontend.config :as config]
  13. [frontend.storage :as storage]
  14. [frontend.components.svg :as svg]
  15. [medley.core :as medley]
  16. [frontend.fs :as fs]
  17. [clojure.string :as string]))
  18. (defn dd [& args]
  19. (apply js/console.debug args))
  20. (defn reset-current-pdf!
  21. []
  22. (state/set-state! :pdf/current nil))
  23. (rum/defcs pdf-highlight-finder < rum/reactive
  24. [state ^js viewer]
  25. (when-let [ref-hl (state/sub :pdf/ref-highlight)]
  26. ;; delay handle: aim to fix page blink
  27. (js/setTimeout #(pdf-utils/scroll-to-highlight viewer ref-hl) 100)
  28. (js/setTimeout #(state/set-state! :pdf/ref-highlight nil) 1000)
  29. nil))
  30. (rum/defc pdf-resizer
  31. [^js viewer]
  32. (let [el-ref (rum/use-ref nil)
  33. adjust-main-size!
  34. (front-utils/debounce
  35. 200 (fn [width]
  36. (let [root-el js/document.documentElement]
  37. (.setProperty (.-style root-el) "--ph-view-container-width" width)
  38. (pdf-utils/adjust-viewer-size! viewer))))]
  39. ;; draggable handler
  40. (rum/use-effect!
  41. (fn []
  42. (when-let [el (and (fn? js/window.interact) (rum/deref el-ref))]
  43. (-> (js/interact el)
  44. (.draggable
  45. (bean/->js
  46. {:listeners
  47. {:move
  48. (fn [^js/MouseEvent e]
  49. (let [width js/document.documentElement.clientWidth
  50. offset (.-left (.-rect e))
  51. el-ratio (.toFixed (/ offset width) 6)
  52. target-el (js/document.getElementById "pdf-layout-container")]
  53. (when target-el
  54. (let [width (str (* el-ratio 100) "vw")]
  55. (.setProperty (.-style target-el) "width" width)
  56. (adjust-main-size! width)))))}}))
  57. (.styleCursor false)
  58. (.on "dragstart" #(.. js/document.documentElement -classList (add "is-resizing-buf")))
  59. (.on "dragend" #(.. js/document.documentElement -classList (remove "is-resizing-buf")))))
  60. #())
  61. [])
  62. [:span.extensions__pdf-resizer {:ref el-ref}]))
  63. (rum/defc pdf-highlights-ctx-menu
  64. [^js viewer {:keys [highlight vw-pos point]}
  65. {:keys [clear-ctx-tip! add-hl! upd-hl! del-hl!]}]
  66. (let [mounted (rum/use-ref false)]
  67. (rum/use-effect!
  68. (fn []
  69. (let [cb #(if-not (rum/deref mounted)
  70. (rum/set-ref! mounted true)
  71. (clear-ctx-tip!))]
  72. (js/document.addEventListener "click" cb)
  73. #(js/document.removeEventListener "click" cb)))
  74. [clear-ctx-tip!]))
  75. ;; TODO: precise position
  76. ;;(when-let [
  77. ;;page-bounding (and highlight (pdf-utils/get-page-bounding viewer (:page highlight)))
  78. ;;])
  79. (let [head-height 0 ;; 48 temp
  80. top (- (+ (:y point) (.. viewer -container -scrollTop)) head-height)
  81. left (:x point)
  82. id (:id highlight)
  83. content (:content highlight)]
  84. (rum/with-context
  85. [[t] i18n/*tongue-context*]
  86. [:ul.extensions__pdf-hls-ctx-menu
  87. {:style {:top top :left left}
  88. :on-click (fn [^js/MouseEvent e]
  89. (when-let [action (.. e -target -dataset -action)]
  90. (case action
  91. "ref"
  92. (pdf-assets/copy-hl-ref! highlight)
  93. "copy"
  94. (do
  95. (front-utils/copy-to-clipboard! (:text content))
  96. (pdf-utils/clear-all-selection))
  97. "del"
  98. (do
  99. (del-hl! highlight)
  100. (pdf-assets/del-ref-block! highlight))
  101. ;; colors
  102. (let [properties {:color action}]
  103. (if-not id
  104. ;; add highlight
  105. (let [highlight (merge highlight
  106. {:id (pdf-utils/gen-uuid)
  107. :properties properties})]
  108. (add-hl! highlight)
  109. (pdf-utils/clear-all-selection)
  110. (pdf-assets/copy-hl-ref! highlight))
  111. ;; update highlight
  112. (do
  113. (upd-hl! (assoc highlight :properties properties)))))))
  114. (clear-ctx-tip!))}
  115. [:li.item-colors
  116. (for [it ["yellow", "blue", "green", "red", "purple"]]
  117. [:a {:key it :data-color it :data-action it} it])]
  118. (and id [:li.item {:data-action "ref"} (t :pdf/copy-ref)])
  119. [:li.item {:data-action "copy"} (t :pdf/copy-text)]
  120. (and id [:li.item {:data-action "del"} (t :delete)])
  121. ])))
  122. (rum/defc pdf-highlights-text-region
  123. [^js viewer vw-hl hl
  124. {:keys [show-ctx-tip!]}]
  125. (let [id (:id hl)
  126. {:keys [rects]} (:position vw-hl)
  127. {:keys [color]} (:properties hl)]
  128. [:div.extensions__pdf-hls-text-region
  129. {:on-click
  130. (fn [e]
  131. (let [x (.-clientX e)
  132. y (.-clientY e)]
  133. (show-ctx-tip! viewer hl {:x x :y y})))}
  134. (map-indexed
  135. (fn [idx rect]
  136. [:div.hls-text-region-item
  137. {:key idx
  138. :style rect
  139. :data-color color}])
  140. rects)]))
  141. (rum/defc pdf-highlights-region-container
  142. [^js viewer page-hls ops]
  143. [:div.hls-region-container
  144. (for [hl page-hls]
  145. (let [vw-hl (update-in hl [:position] #(pdf-utils/scaled-to-vw-pos viewer %))]
  146. (rum/with-key (pdf-highlights-text-region viewer vw-hl hl ops) (:id hl))
  147. ))])
  148. (rum/defc pdf-highlight-area-selection
  149. [^js viewer]
  150. (let [^js viewer-clt (.. viewer -viewer -classList)
  151. *el (rum/use-ref nil)
  152. *cnt-el (rum/use-ref nil)
  153. *cnt-rect (rum/use-ref nil)
  154. should-start (fn [^js e] (and e (.-altKey e)))
  155. [start-coord, set-start-coord!] (rum/use-state nil)
  156. [end-coord, set-end-coord!] (rum/use-state nil)
  157. reset-coords #(do
  158. (set-start-coord! nil)
  159. (set-end-coord! nil))
  160. calc-coords (fn [page-x page-y]
  161. (when-let [cnt-el (or (rum/deref *cnt-el)
  162. (when-let [cnt-el (.querySelector (.closest (rum/deref *el) ".extensions__pdf-viewer-cnt") ".extensions__pdf-viewer")]
  163. (rum/set-ref! *cnt-el cnt-el) cnt-el))]
  164. (let [cnt-rect (rum/deref *cnt-rect)
  165. cnt-rect (or cnt-rect (bean/->clj (.toJSON (.getBoundingClientRect cnt-el))))
  166. _ (rum/set-ref! *cnt-rect cnt-rect)]
  167. {:x (- page-x (:left cnt-rect) (.-scrollLeft cnt-el))
  168. :y (-> page-y
  169. (- (:top cnt-rect))
  170. (+ (.-scrollTop cnt-el)))})))
  171. calc-pos (fn [start end]
  172. {:left (min (:x start) (:x end))
  173. :top (min (:y start) (:y end))
  174. :width (js/Math.abs (- (:x end) (:x start)))
  175. :height (js/Math.abs (- (:y end) (:y start)))})
  176. disable-text-selection! #(js-invoke viewer-clt (if % "add" "remove") "disabled-text-selection")
  177. fn-move (rum/use-callback
  178. (fn [^js/MouseEvent e]
  179. (set-end-coord! (calc-coords (.-pageX e) (.-pageY e))))
  180. [])]
  181. (rum/use-effect!
  182. (fn []
  183. (when-let [^js/HTMLElement root (.closest (rum/deref *el) ".extensions__pdf-container")]
  184. (let [fn-start (fn [^js/MouseEvent e]
  185. (when (should-start e)
  186. (set-start-coord! (calc-coords (.-pageX e) (.-pageY e)))
  187. (disable-text-selection! true)
  188. (.addEventListener root "mousemove" fn-move)))
  189. fn-end (fn [^js/MouseEvent e]
  190. (when-let [start start-coord]
  191. (let [end (calc-coords (.-pageX e) (.-pageY e))
  192. pos (calc-pos start end)]
  193. (if (and (> (:width pos) 10)
  194. (> (:height pos) 10))
  195. (do
  196. ;; export area highlight
  197. (dd "[selection end] :start" start ":end" end ":pos" pos))
  198. ;; reset
  199. (reset-coords)))
  200. (disable-text-selection! false)
  201. (.removeEventListener root "mousemove" fn-move)))]
  202. (doto root
  203. (.addEventListener "mousedown" fn-start)
  204. (.addEventListener "mouseup" fn-end #js {:once true}))
  205. ;; destroy
  206. #(doto root
  207. (.removeEventListener "mousedown" fn-start)
  208. (.removeEventListener "mouseup" fn-end)))))
  209. [start-coord])
  210. [:div.extensions__pdf-area-selection
  211. {:ref *el}
  212. (when (and start-coord end-coord)
  213. [:div.shadow-rect {:style (calc-pos start-coord end-coord)}])]))
  214. (rum/defc pdf-highlights
  215. [^js el ^js viewer initial-hls loaded-pages {:keys [set-dirty-hls!]}]
  216. (let [^js doc (.-ownerDocument el)
  217. ^js win (.-defaultView doc)
  218. *mounted (rum/use-ref false)
  219. [sel-state, set-sel-state!] (rum/use-state {:range nil :collapsed nil :point nil})
  220. [highlights, set-highlights!] (rum/use-state initial-hls)
  221. [tip-state, set-tip-state!] (rum/use-state {:highlight nil :vw-pos nil :point nil})
  222. clear-ctx-tip! #(set-tip-state! {})
  223. show-ctx-tip! (fn [^js viewer hl point]
  224. (let [vw-pos (pdf-utils/scaled-to-vw-pos viewer (:position hl))]
  225. (set-tip-state! {:highlight hl :vw-pos vw-pos :point point})))
  226. add-hl! (fn [hl] (when (:id hl)
  227. ;; fix js object
  228. (let [highlights (pdf-utils/fix-nested-js highlights)]
  229. (set-highlights! (conj highlights hl)))))
  230. upd-hl! (fn [hl]
  231. (let [highlights (pdf-utils/fix-nested-js highlights)]
  232. (when-let [[target-idx] (medley/find-first
  233. #(= (:id (second %)) (:id hl))
  234. (medley/indexed highlights))]
  235. (set-highlights! (assoc-in highlights [target-idx] hl)))))
  236. del-hl! (fn [hl] (when-let [id (:id hl)] (set-highlights! (into [] (remove #(= id (:id %)) highlights)))))]
  237. ;; consume dirtied
  238. (rum/use-effect!
  239. (fn []
  240. (if (rum/deref *mounted)
  241. (set-dirty-hls! highlights)
  242. (rum/set-ref! *mounted true)))
  243. [highlights])
  244. ;; selection events
  245. (rum/use-effect!
  246. (fn []
  247. (let [fn-selection-ok
  248. (fn [^js/MouseEvent e]
  249. (let [^js/Selection selection (js/document.getSelection)
  250. ^js/Range sel-range (.getRangeAt selection 0)]
  251. (cond
  252. (.-isCollapsed selection)
  253. (set-sel-state! {:collapsed true})
  254. (and sel-range (.contains el (.-commonAncestorContainer sel-range)))
  255. (set-sel-state! {:collapsed false :range sel-range :point {:x (.-clientX e) :y (.-clientY e)}}))))
  256. fn-selection
  257. (fn []
  258. (let [*dirty (volatile! false)
  259. fn-dirty #(vreset! *dirty true)]
  260. (js/document.addEventListener "selectionchange" fn-dirty)
  261. (js/document.addEventListener "mouseup"
  262. (fn [^js e]
  263. (and @*dirty (fn-selection-ok e))
  264. (js/document.removeEventListener "selectionchange" fn-dirty))
  265. #js {:once true})))
  266. fn-resize
  267. (partial pdf-utils/adjust-viewer-size! viewer)]
  268. ;;(doto (.-eventBus viewer))
  269. (doto el
  270. (.addEventListener "mousedown" fn-selection))
  271. (doto win
  272. (.addEventListener "resize" fn-resize))
  273. ;; destroy
  274. #(do
  275. ;;(doto (.-eventBus viewer))
  276. (doto el
  277. (.removeEventListener "mousedown" fn-selection))
  278. (doto win
  279. (.removeEventListener "resize" fn-resize)))))
  280. [viewer])
  281. ;; selection context menu
  282. (rum/use-effect!
  283. (fn []
  284. (when-let [^js sel-range (and (not (:collapsed sel-state)) (:range sel-state))]
  285. (when-let [page-info (pdf-utils/get-page-from-range sel-range)]
  286. (when-let [sel-rects (pdf-utils/get-range-rects<-page-cnt sel-range (:page-el page-info))]
  287. (let [page (int (:page-number page-info))
  288. ^js point (:point sel-state)
  289. ^js bounding (pdf-utils/get-bounding-rect sel-rects)
  290. vw-pos {:bounding bounding :rects sel-rects :page page}
  291. sc-pos (pdf-utils/vw-to-scaled-pos viewer vw-pos)]
  292. ;; TODO: debug
  293. ;;(dd "[VW x SC] ====>" vw-pos sc-pos)
  294. ;;(dd "[Range] ====> [" page-info "]" (.toString sel-range) point)
  295. ;;(dd "[Rects] ====>" sel-rects " [Bounding] ====>" bounding)
  296. (let [hl {:id nil
  297. :page page
  298. :position sc-pos
  299. :content {:text (.toString sel-range)}
  300. :properties {}}]
  301. ;; show context menu
  302. (set-tip-state! {:highlight hl
  303. :vw-pos vw-pos
  304. :point point})))))))
  305. [(:range sel-state)])
  306. ;; render hls
  307. (rum/use-effect!
  308. (fn []
  309. ;;(dd "=== rebuild highlights ===" (count highlights))
  310. (when-let [grouped-hls (and (sequential? highlights) (group-by :page highlights))]
  311. (doseq [page loaded-pages]
  312. (when-let [^js/HTMLDivElement hls-layer (pdf-utils/resolve-hls-layer! viewer page)]
  313. (let [page-hls (get grouped-hls page)]
  314. (rum/mount
  315. ;; TODO: area & text hls
  316. (pdf-highlights-region-container viewer page-hls {:show-ctx-tip! show-ctx-tip!})
  317. hls-layer)))))
  318. ;; destroy
  319. #())
  320. [loaded-pages highlights])
  321. [:div.extensions__pdf-highlights-cnt
  322. ;; hl context tip menu
  323. (if (:highlight tip-state)
  324. (js/ReactDOM.createPortal
  325. (pdf-highlights-ctx-menu
  326. viewer tip-state
  327. {:clear-ctx-tip! clear-ctx-tip!
  328. :add-hl! add-hl!
  329. :del-hl! del-hl!
  330. :upd-hl! upd-hl!})
  331. (.querySelector el ".pp-holder")))
  332. ;; debug highlights anchor
  333. ;;(if (seq highlights)
  334. ;; [:ul.extensions__pdf-highlights
  335. ;; (for [hl highlights]
  336. ;; [:li
  337. ;; [:a
  338. ;; {:on-click #(pdf-utils/scroll-to-highlight viewer hl)}
  339. ;; (str "#" (:id hl) "# ")]
  340. ;; (:text (:content hl))])
  341. ;; ])
  342. ;; refs
  343. (pdf-highlight-finder viewer)
  344. ;; area selection container
  345. (pdf-highlight-area-selection viewer)]))
  346. (rum/defc pdf-settings
  347. [^js viewer theme {:keys [hide-settings! select-theme!]}]
  348. (let [*el-popup (rum/use-ref nil)]
  349. (rum/use-effect!
  350. (fn []
  351. (let [el-popup (rum/deref *el-popup)
  352. cb (fn [^js e]
  353. (and (= e.which 27) (hide-settings!)))]
  354. (js/setTimeout #(.focus el-popup))
  355. (.addEventListener el-popup "keyup" cb)
  356. #(.removeEventListener el-popup "keyup" cb)))
  357. [])
  358. [:div.extensions__pdf-settings.hls-popup-wrap.visible
  359. {:on-click (fn [^js/MouseEvent e]
  360. (let [target (.-target e)]
  361. (when-not (.contains (rum/deref *el-popup) target)
  362. (hide-settings!))))}
  363. [:div.extensions__pdf-settings-inner.hls-popup-box
  364. {:ref *el-popup
  365. :tab-index -1}
  366. [:div.extensions__pdf-settings-item.theme-picker
  367. (map (fn [it]
  368. [:button.flex.items-center.justify-center
  369. {:key it :class it :on-click #(do (select-theme! it) (hide-settings!))}
  370. (if (= theme it) (svg/check))])
  371. ["light", "warm", "dark"])
  372. ]]]))
  373. (rum/defc pdf-outline-item
  374. [^js viewer
  375. {:keys [title items href parent dest expanded] :as node}
  376. {:keys [upt-outline-node!] :as ops}]
  377. (let [has-child? (seq items)
  378. expanded? (boolean expanded)]
  379. [:div.extensions__pdf-outline-item
  380. {:class (front-utils/classnames [{:has-children has-child? :is-expand expanded?}])}
  381. [:div.inner
  382. [:a
  383. {:href "javascript:void(0);"
  384. :data-dest (js/JSON.stringify (bean/->js dest))
  385. :on-click (fn [^js/MouseEvent e]
  386. (let [target (.-target e)]
  387. (if (.closest target "i")
  388. (let [path (map #(if (re-find #"\d+" %) (int %) (keyword %))
  389. (string/split parent #"\-"))]
  390. (.preventDefault e)
  391. (upt-outline-node! path {:expanded (not expanded?)}))
  392. (when-let [^js dest (and dest (bean/->js dest))]
  393. (.goToDestination (.-linkService viewer) dest)))))}
  394. [:i.arrow svg/arrow-right-v2]
  395. [:span title]]]
  396. ;; children
  397. (when (and has-child? expanded?)
  398. [:div.children
  399. (map-indexed
  400. (fn [idx itm]
  401. (let [parent (str parent "-items-" idx)]
  402. (rum/with-key
  403. (pdf-outline-item
  404. viewer
  405. (merge itm {:parent parent})
  406. ops) parent))) items)])]))
  407. (rum/defc pdf-outline
  408. [^js viewer visible? hide!]
  409. (when-let [^js pdf-doc (and viewer (.-pdfDocument viewer))]
  410. (let [*el-outline (rum/use-ref nil)
  411. [outline-data, set-outline-data!] (rum/use-state [])
  412. upt-outline-node! (rum/use-callback
  413. (fn [path attrs]
  414. (set-outline-data! (update-in outline-data path merge attrs)))
  415. [outline-data])]
  416. (rum/use-effect!
  417. (fn []
  418. (p/catch
  419. (p/let [^js data (.getOutline pdf-doc)]
  420. (when-let [data (and data (.map data (fn [^js it]
  421. (set! (.-href it) (.. viewer -linkService (getDestinationHash (.-dest it))))
  422. (set! (.-expanded it) false)
  423. it)))])
  424. (set-outline-data! (bean/->clj data)))
  425. (fn [e]
  426. (js/console.error "[Load outline Error]" e))))
  427. [pdf-doc])
  428. (rum/use-effect!
  429. (fn []
  430. (let [el-outline (rum/deref *el-outline)
  431. cb (fn [^js e]
  432. (and (= e.which 27) (hide!)))]
  433. (js/setTimeout #(.focus el-outline))
  434. (.addEventListener el-outline "keyup" cb)
  435. #(.removeEventListener el-outline "keyup" cb)))
  436. [])
  437. [:div.extensions__pdf-outline-wrap.hls-popup-wrap
  438. {:class (front-utils/classnames [{:visible visible?}])
  439. :on-click (fn [^js/MouseEvent e]
  440. (let [target (.-target e)]
  441. (when-not (.contains (rum/deref *el-outline) target)
  442. (hide!))))}
  443. [:div.extensions__pdf-outline.hls-popup-box
  444. {:ref *el-outline
  445. :tab-index -1}
  446. (if (seq outline-data)
  447. [:section
  448. (map-indexed (fn [idx itm]
  449. (rum/with-key
  450. (pdf-outline-item
  451. viewer
  452. (merge itm {:parent idx})
  453. {:upt-outline-node! upt-outline-node!})
  454. idx))
  455. outline-data)]
  456. [:section.is-empty "No outlines"])]])))
  457. (rum/defc docinfo-display
  458. [info]
  459. [:div.extensions__pdf-doc-info
  460. (for [[k v] info
  461. :let [k (pr-str k)]]
  462. [:p {:key k} [:strong k] " " [:i (pr-str v)]])])
  463. (defn make-docinfo-in-modal
  464. [info]
  465. (fn [close-fn]
  466. (docinfo-display info)))
  467. (rum/defc pdf-toolbar
  468. [^js viewer]
  469. (let [[outline-visible?, set-outline-visible!] (rum/use-state false)
  470. [settings-visible?, set-settings-visible!] (rum/use-state false)
  471. [viewer-theme, set-viewer-theme!] (rum/use-state (or (storage/get "ls-pdf-viewer-theme") "light"))]
  472. ;; themes hooks
  473. (rum/use-effect!
  474. (fn []
  475. (when-let [^js el (js/document.getElementById "pdf-layout-container")]
  476. (set! (. (. el -dataset) -theme) viewer-theme)
  477. (storage/set "ls-pdf-viewer-theme" viewer-theme)
  478. #(js-delete (. el -dataset) "theme")))
  479. [viewer-theme])
  480. (rum/with-context
  481. [[t] i18n/*tongue-context*]
  482. [:div.extensions__pdf-toolbar
  483. [:div.inner
  484. [:div.r.flex
  485. ;; appearance
  486. [:a.button
  487. {:on-click #(set-settings-visible! (not settings-visible?))}
  488. (svg/adjustments 18)]
  489. ;; zoom
  490. [:a.button
  491. {:on-click (partial pdf-utils/zoom-out-viewer viewer)}
  492. (svg/zoom-out 18)]
  493. [:a.button
  494. {:on-click (partial pdf-utils/zoom-in-viewer viewer)}
  495. (svg/zoom-in 18)]
  496. [:a.button
  497. {:on-click #(set-outline-visible! (not outline-visible?))}
  498. (svg/view-list 16)]
  499. ;; metadata
  500. [:a.button.is-info
  501. {:on-click #(do
  502. (p/let [ret (pdf-utils/get-meta-data$ viewer)]
  503. (state/set-modal! (make-docinfo-in-modal ret))))}
  504. (svg/info)]
  505. [:a.button
  506. {:on-click #(state/set-state! :pdf/current nil)}
  507. (t :close)]]]
  508. ;; contents outline
  509. (pdf-outline viewer outline-visible? #(set-outline-visible! false))
  510. ;; settings
  511. (and settings-visible? (pdf-settings
  512. viewer
  513. viewer-theme
  514. {:hide-settings! #(set-settings-visible! false)
  515. :select-theme! #(set-viewer-theme! %)}))])))
  516. (rum/defc pdf-viewer
  517. [url initial-hls ^js pdf-document ops]
  518. ;;(dd "==== render pdf-viewer ====")
  519. (let [*el-ref (rum/create-ref)
  520. [state, set-state!] (rum/use-state {:viewer nil :bus nil :link nil :el nil})
  521. [ano-state, set-ano-state!] (rum/use-state {:loaded-pages []})]
  522. ;; instant pdfjs viewer
  523. (rum/use-effect!
  524. (fn [] (let [^js event-bus (js/pdfjsViewer.EventBus.)
  525. ^js link-service (js/pdfjsViewer.PDFLinkService. #js {:eventBus event-bus :externalLinkTarget 2})
  526. ^js el (rum/deref *el-ref)
  527. ^js viewer (js/pdfjsViewer.PDFViewer.
  528. #js {:container el
  529. :eventBus event-bus
  530. :linkService link-service
  531. :enhanceTextSelection true
  532. :removePageBorders true})]
  533. (. link-service setDocument pdf-document)
  534. (. link-service setViewer viewer)
  535. ;; TODO: debug
  536. (set! (. js/window -lsPdfViewer) viewer)
  537. (p/then (. viewer setDocument pdf-document)
  538. #(set-state! {:viewer viewer :bus event-bus :link link-service :el el})))
  539. ;;TODO: destroy
  540. #(.destroy pdf-document))
  541. [])
  542. ;; interaction events
  543. (rum/use-effect!
  544. (fn []
  545. (when-let [^js viewer (:viewer state)]
  546. (let [^js el (rum/deref *el-ref)
  547. fn-textlayer-ready
  548. (fn [^js p]
  549. (set-ano-state! {:loaded-pages (conj (:loaded-pages ano-state) (int (.-pageNumber p)))}))
  550. fn-page-ready
  551. (fn []
  552. (set! (. viewer -currentScaleValue) "auto"))]
  553. (doto (.-eventBus viewer)
  554. (.on "pagesinit" fn-page-ready)
  555. (.on "textlayerrendered" fn-textlayer-ready))
  556. #(do
  557. (doto (.-eventBus viewer)
  558. (.off "pagesinit" fn-page-ready)
  559. (.off "textlayerrendered" fn-textlayer-ready))))))
  560. [(:viewer state)
  561. (:loaded-pages ano-state)])
  562. (let [^js viewer (:viewer state)]
  563. [:div.extensions__pdf-viewer-cnt
  564. [:div.extensions__pdf-viewer {:ref *el-ref}
  565. [:div.pdfViewer "viewer pdf"]
  566. [:div.pp-holder]
  567. (when viewer
  568. [(rum/with-key
  569. (pdf-highlights
  570. (:el state) viewer
  571. initial-hls (:loaded-pages ano-state)
  572. ops) "pdf-highlights")])]
  573. (when viewer
  574. [(rum/with-key (pdf-resizer viewer) "pdf-resizer")
  575. (rum/with-key (pdf-toolbar viewer) "pdf-toolbar")])])))
  576. (rum/defc pdf-loader
  577. [{:keys [url hls-file] :as pdf-current}]
  578. (let [*doc-ref (rum/use-ref nil)
  579. [state, set-state!] (rum/use-state {:error nil :pdf-document nil :status nil})
  580. [hls-state, set-hls-state!] (rum/use-state {:initial-hls nil :latest-hls nil})
  581. repo-cur (state/get-current-repo)
  582. repo-dir (config/get-repo-dir repo-cur)
  583. set-dirty-hls! (fn [latest-hls] ;; TODO: incremental
  584. (set-hls-state! {:initial-hls [] :latest-hls latest-hls}))]
  585. ;; load highlights
  586. (rum/use-effect!
  587. (fn []
  588. (p/catch
  589. (p/let [data (pdf-assets/load-hls-data$ pdf-current)
  590. highlights (:highlights data)]
  591. (set-hls-state! {:initial-hls highlights}))
  592. ;; error
  593. (fn [e]
  594. (js/console.error "[load hls error]" e)
  595. (set-hls-state! {:initial-hls []})))
  596. ;; cancel
  597. #())
  598. [hls-file])
  599. ;; cache highlights
  600. (rum/use-effect!
  601. (fn []
  602. (when-let [hls (:latest-hls hls-state)]
  603. (p/catch
  604. (pdf-assets/persist-hls-data$ pdf-current hls)
  605. ;; write hls file error
  606. (fn [e]
  607. (js/console.error "[write hls error]" e)))))
  608. [(:latest-hls hls-state)])
  609. ;; load document
  610. (rum/use-effect!
  611. (fn []
  612. (let [get-doc$ (fn [^js opts] (.-promise (js/pdfjsLib.getDocument opts)))
  613. own-doc (rum/deref *doc-ref)
  614. opts {:url url
  615. :ownerDocument js/document
  616. ;;:cMapUrl "./js/pdfjs/cmaps/"
  617. :cMapUrl "https://cdn.jsdelivr.net/npm/[email protected]/cmaps/"
  618. :cMapPacked true}]
  619. (p/finally
  620. (p/catch (p/then
  621. (do
  622. (set-state! {:status :loading})
  623. (get-doc$ (clj->js opts)))
  624. #(set-state! {:pdf-document %}))
  625. #(set-state! {:error %}))
  626. #(set-state! {:status :completed}))
  627. #()))
  628. [url])
  629. (rum/use-effect!
  630. (fn []
  631. (dd "[ERROR loader]" (:error state)))
  632. [(:error state)])
  633. [:div.extensions__pdf-loader {:ref *doc-ref}
  634. (let [status-doc (:status state)
  635. initial-hls (:initial-hls hls-state)]
  636. (if (or (= status-doc :loading)
  637. (nil? initial-hls))
  638. [:div.flex.justify-center.items-center.h-screen.text-gray-500.text-md
  639. "Downloading PDF file " url]
  640. [(rum/with-key (pdf-viewer
  641. url initial-hls
  642. (:pdf-document state)
  643. {:set-dirty-hls! set-dirty-hls!}) "pdf-viewer")]))]))
  644. (rum/defc pdf-container
  645. [{:keys [identity] :as pdf-current}]
  646. (let [[prepared set-prepared!] (rum/use-state false)
  647. [ready set-ready!] (rum/use-state false)]
  648. ;; load assets
  649. (rum/use-effect!
  650. (fn []
  651. (p/then
  652. (pdf-utils/load-base-assets$)
  653. (fn [] (set-prepared! true))))
  654. [])
  655. ;; refresh loader
  656. (rum/use-effect!
  657. (fn []
  658. (js/setTimeout #(set-ready! true) 100)
  659. #(set-ready! false))
  660. [identity])
  661. [:div#pdf-layout-container.extensions__pdf-container
  662. (if (and prepared identity ready)
  663. (pdf-loader pdf-current))]))
  664. (rum/defc playground-effects
  665. [active]
  666. (rum/use-effect!
  667. (fn []
  668. (let [flg "is-pdf-active"
  669. ^js cls (.-classList js/document.body)]
  670. (and active (.add cls flg))
  671. #(.remove cls flg)))
  672. [active])
  673. nil)
  674. (rum/defcs playground
  675. < rum/static
  676. rum/reactive
  677. [state]
  678. (let [pdf-current (state/sub :pdf/current)]
  679. [:div.extensions__pdf-playground
  680. (playground-effects (not (nil? pdf-current)))
  681. (when pdf-current
  682. (js/ReactDOM.createPortal
  683. (pdf-container pdf-current)
  684. (js/document.querySelector "#app-single-container")))]))