Browse Source

improve(pdf): modifiable area highlight & persist area image data

charlie 4 years ago
parent
commit
441a5ec077

+ 7 - 2
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))

+ 10 - 3
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])))))

+ 68 - 5
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}]]))))))

+ 98 - 13
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)))))
 

+ 30 - 0
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;
+      }
+    }
   }
 }