diff --git a/src/electron/electron/handler.cljs b/src/electron/electron/handler.cljs index a15e82dc19..65f6cb4266 100644 --- a/src/electron/electron/handler.cljs +++ b/src/electron/electron/handler.cljs @@ -2,6 +2,7 @@ (:require ["electron" :refer [ipcMain dialog app]] [cljs-bean.core :as bean] ["fs" :as fs] + ["buffer" :as buffer] ["fs-extra" :as fs-extra] ["path" :as path] [electron.fs-watcher :as watcher] @@ -55,8 +56,12 @@ (defmethod handle :writeFile [_window [_ path content]] ;; TODO: handle error - (fs/writeFileSync path content) - (fs/statSync path)) + (let [^js Buf (.-Buffer buffer) + ^js content (if (instance? js/ArrayBuffer content) + (.from Buf content) content)] + + (fs/writeFileSync path content) + (fs/statSync path))) (defmethod handle :rename [_window [_ old-path new-path]] (fs/renameSync old-path new-path)) diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index 864feec149..90d754e0d0 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -587,10 +587,12 @@ (let [block-id (uuid id) block (db/pull-block block-id) block-type (keyword (get-in block [:block/properties :ls-type])) + hl-type (get-in block [:block/properties :hl-type]) repo (state/get-current-repo)] (if block [:div.block-ref-wrap.inline {:data-type (name (or block-type :default)) + :data-hl-type hl-type :on-mouse-down (fn [e] (util/stop e) @@ -1471,7 +1473,8 @@ (->elem elem (merge - {:id anchor} + {:id anchor + :data-hl-type (:hl-type properties)} (when (and marker (not (string/blank? marker)) (not= "nil" marker)) @@ -1496,9 +1499,13 @@ {:on-click #(case block-type ;; pdf annotation :annotation (pdf-assets/open-block-ref! t) - :default)} + (.preventDefault %))} - (str "P" (or (:page properties) "?"))])) + [:span.hl-page (str "P" (or (:hl-page properties) "?"))] + + (when-let [st (and (= :area (keyword (:hl-type properties))) + (:hl-hash properties))] + (pdf-assets/area-display t))])) [[:span.opacity-50 "Click here to start writing, type '/' to see all the commands."]]) [tags]))))) diff --git a/src/main/frontend/extensions/pdf/assets.cljs b/src/main/frontend/extensions/pdf/assets.cljs index 57c9118028..b698af6774 100644 --- a/src/main/frontend/extensions/pdf/assets.cljs +++ b/src/main/frontend/extensions/pdf/assets.cljs @@ -56,6 +56,51 @@ (when-let [hls-file (and target-key (str config/local-assets-dir "/" target-key ".edn"))] (load-hls-data$ {:hls-file hls-file}))) +(defn area-highlight? + [hl] + (and hl (not (nil? (get-in hl [:content :image]))))) + +(defn persist-hl-area-image$ + [^js viewer current hl {:keys [top left width height] :as vw-bounding}] + (when-let [^js canvas (and (:key current) (.-canvas (.getPageView viewer (dec (:page hl)))))] + (let [^js doc (.-ownerDocument canvas) + ^js canvas' (.createElement doc "canvas") + dpr js/window.devicePixelRatio + repo-cur (state/get-current-repo) + repo-dir (config/get-repo-dir repo-cur)] + + (set! (. canvas' -width) width) + (set! (. canvas' -height) height) + + (when-let [^js ctx (.getContext canvas' "2d")] + (.drawImage + ctx canvas + (* left dpr) (* top dpr) (* width dpr) (* height dpr) + 0 0 width height) + + (let [callback (fn [^js png] + ;; write image file + (p/catch + (p/let [_ (js/console.time :write-area-image) + ^js png (.arrayBuffer png) + {:keys [key]} current + ;; dir + fname (str (:page hl) "_" (:id hl)) + fdir (str config/local-assets-dir "/" key) + _ (fs/mkdir-if-not-exists (str repo-dir "/" fdir)) + _ (fs/write-file! repo-cur repo-dir (str fdir "/" fname ".png") png {:skip-mtime? true})] + + (js/console.timeEnd :write-area-image)) + + (fn [err] + (js/console.error "[write area image Error]" err))))] + + (.toBlob canvas' callback)) + )))) + +(defn unlink-hl-area-image$ + []) + (defn resolve-ref-page [page-name] (let [page-name (str "hls__" page-name) @@ -76,14 +121,18 @@ (do (js/console.debug "[existed ref block]" ref-block) ref-block) - (let [text (:text content)] ;; TODO: image + (let [text (:text content) + wrap-props #(if-let [hash (:image content)] + (assoc % :hl-type "area" :hl-hash hash) %)] + (editor-handler/api-insert-new-block! text {:page (:block/name ref-page) :custom-uuid id - :properties {:ls-type "annotation" - :page page - :id (str id) ;; force custom uuid - }})))))) + :properties (wrap-props + {:ls-type "annotation" + :hl-page page + ;; force custom uuid + :id (str id)})})))))) (defn del-ref-block! [{:keys [id]}] @@ -177,3 +226,17 @@ (let [files (.-files (.-target e))] (upload-asset! page files refresh-file!)) )}]]])))) + +(rum/defc area-display + [block] + (let [id (:block/uuid block) + props (:block/properties block)] + (when-let [page (db-utils/pull (:db/id (:block/page block)))] + (when-let [group-key (string/replace-first (:block/original-name page) #"^hls__" "")] + (when-let [hl-page (:hl-page props)] + ;; TODO: async? + (let [asset-path (editor-handler/make-asset-url + (str "/" config/local-assets-dir "/" group-key "/" (str hl-page "_" id ".png")))] + + [:span.hl-area + [:img {:src asset-path}]])))))) \ No newline at end of file diff --git a/src/main/frontend/extensions/pdf/highlights.cljs b/src/main/frontend/extensions/pdf/highlights.cljs index 1dfc5662c9..906e4257d1 100644 --- a/src/main/frontend/extensions/pdf/highlights.cljs +++ b/src/main/frontend/extensions/pdf/highlights.cljs @@ -168,18 +168,95 @@ (rum/defc pdf-highlight-area-region [^js viewer vw-hl hl - {:keys [show-ctx-tip!]}] + {:keys [show-ctx-tip! upd-hl!]}] - (when-let [vw-bounding (get-in vw-hl [:position :bounding])] - (let [{:keys [color]} (:properties hl)] - [:div.extensions__pdf-hls-area-region - {:style vw-bounding - :data-color color - :on-click (fn [^js/MouseEvent e] - (let [x (.-clientX e) - y (.-clientY e)] + (let [*el (rum/use-ref nil) + *dirty (rum/use-ref nil)] - (show-ctx-tip! viewer hl {:x x :y y})))}]))) + ;; resizable + (rum/use-effect! + (fn [] + (let [^js el (rum/deref *el) + ^js it (-> (js/interact el) + (.resizable + (bean/->js + {:edges {:left true :right true :top true :bottom true} + :listeners {:start (fn [^js/MouseEvent e] + (rum/set-ref! *dirty true)) + + :end (fn [^js/MouseEvent e] + (let [vw-pos (:position vw-hl) + ^js target (. e -target) + ^js vw-rect (. e -rect) + [dx, dy] (mapv #(let [val (.getAttribute target (str "data-" (name %)))] + (if-not (nil? val) (js/parseFloat val) 0)) [:x :y]) + to-top (+ (get-in vw-pos [:bounding :top]) dy) + to-left (+ (get-in vw-pos [:bounding :left]) dx) + to-w (. vw-rect -width) + to-h (. vw-rect -height) + to-vw-pos (update vw-pos :bounding assoc + :top to-top + :left to-left + :width to-w + :height to-h) + + to-sc-pos (pdf-utils/vw-to-scaled-pos viewer to-vw-pos)] + + ;; TODO: exception + (let [hl (assoc hl :position to-sc-pos) + hl (assoc-in hl [:content :image] (js/Date.now))] + (pdf-assets/persist-hl-area-image$ viewer (:pdf/current @state/state) hl (:bounding to-vw-pos)) + (upd-hl! hl)) + + ;; reset dom effects + (set! (.. target -style -transform) (str "translate(0, 0)")) + (.removeAttribute target "data-x") + (.removeAttribute target "data-y") + + (js/setTimeout #(rum/set-ref! *dirty false)))) + + :move (fn [^js/MouseEvent e] + (let [^js/HTMLElement target (.-target e) + x (.getAttribute target "data-x") + y (.getAttribute target "data-y") + bx (if-not (nil? x) (js/parseFloat x) 0) + by (if-not (nil? y) (js/parseFloat y) 0)] + + ;; update element style + (set! (.. target -style -width) (str (.. e -rect -width) "px")) + (set! (.. target -style -height) (str (.. e -rect -height) "px")) + + ;; translate when resizing from top or left edges + (let [ax (+ bx (.. e -deltaRect -left)) + ay (+ by (.. e -deltaRect -top))] + + (set! (.. target -style -transform) (str "translate(" ax "px, " ay "px)")) + + ;; cache pos + (.setAttribute target "data-x" ax) + (.setAttribute target "data-y" ay)) + ))} + :modifiers [;; minimum + (js/interact.modifiers.restrictSize + (bean/->js {:min {:width 60 :height 25}}))] + :inertia true}) + ))] + ;; destroy + #(.unset it))) + [hl]) + + (when-let [vw-bounding (get-in vw-hl [:position :bounding])] + (let [{:keys [color]} (:properties hl)] + [:div.extensions__pdf-hls-area-region + {:ref *el + :style vw-bounding + :data-color color + :on-click (fn [^js/MouseEvent e] + (when-not (rum/deref *dirty) + (let [x (.-clientX e) + y (.-clientY e)] + + (show-ctx-tip! viewer hl {:x x :y y}))))}])))) (rum/defc pdf-highlights-region-container [^js viewer page-hls ops] @@ -273,7 +350,7 @@ hl {:id nil :page page-number :position sc-pos - :content {:text "[AREA IMAGE]" :image true} + :content {:text "[:span]" :image (js/Date.now)} :properties {}}] ;; ctx tips @@ -331,7 +408,13 @@ add-hl! (fn [hl] (when (:id hl) ;; fix js object (let [highlights (pdf-utils/fix-nested-js highlights)] - (set-highlights! (conj highlights hl))))) + (set-highlights! (conj highlights hl))) + + (when-let [vw-pos (and (pdf-assets/area-highlight? hl) + (pdf-utils/scaled-to-vw-pos viewer (:position hl)))] + ;; exceptions + (pdf-assets/persist-hl-area-image$ viewer (:pdf/current @state/state) + hl (:bounding vw-pos))))) upd-hl! (fn [hl] (let [highlights (pdf-utils/fix-nested-js highlights)] @@ -443,7 +526,9 @@ (rum/mount ;; TODO: area & text hls - (pdf-highlights-region-container viewer page-hls {:show-ctx-tip! show-ctx-tip!}) + (pdf-highlights-region-container + viewer page-hls {:show-ctx-tip! show-ctx-tip! + :upd-hl! upd-hl!}) hls-layer))))) diff --git a/src/main/frontend/extensions/pdf/pdf.css b/src/main/frontend/extensions/pdf/pdf.css index f856892fdc..6bef9888dc 100644 --- a/src/main/frontend/extensions/pdf/pdf.css +++ b/src/main/frontend/extensions/pdf/pdf.css @@ -260,6 +260,7 @@ z-index: 2; background-color: #FCD713FF; mix-blend-mode: multiply; + touch-action: none; &[data-color=yellow] { background-color: var(--ph-highlight-color-yellow); @@ -503,6 +504,15 @@ &-wrap { &[data-type=annotation] { } + + &[data-hl-type=area] { + display: block; + + .block-ref { + display: block; + border: none; + } + } } } @@ -518,6 +528,26 @@ content: "📔📌 "; } } + + [data-hl-type=area] { + display: flex; + margin-bottom: 10px; + flex-direction: column; + + a.prefix-link { + display: inline; + } + } + + .hl-area { + display: block; + padding: 10px 0; + + img { + margin: 0; + box-shadow: none; + } + } } }