Selaa lähdekoodia

imporve(pdf): outline viewer

charlie 4 vuotta sitten
vanhempi
sitoutus
f62d3c038d

+ 7 - 1
src/main/frontend/components/svg.cljs

@@ -604,4 +604,10 @@
   ([] (zoom-out 16))
   ([] (zoom-out 16))
   ([size]
   ([size]
    [:svg {:fill "none" :width size :height size :viewBox "0 0 24 24" :stroke "currentColor"}
    [:svg {:fill "none" :width size :height size :viewBox "0 0 24 24" :stroke "currentColor"}
-    [:path {:stroke-linecap "round" :stroke-linejoin "round" :stroke-width "2" :d "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"}]]))
+    [:path {:stroke-linecap "round" :stroke-linejoin "round" :stroke-width "2" :d "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"}]]))
+
+(defn view-list
+  ([] (view-list 16))
+  ([size]
+   [:svg.icon {:viewBox "0 0 1024 1024" :width size :height size :fill "none" :stroke "currentColor"}
+    [:path {:d "M134.976 853.312H89.6c-26.56 0-46.912-20.928-46.912-48.256 0-27.392 20.352-48.32 46.912-48.32h45.376c26.624 0 46.912 20.928 46.912 48.32 0 27.328-20.288 48.256-46.912 48.256zM134.976 560.32H89.6C63.04 560.32 42.688 539.392 42.688 512s20.352-48.32 46.912-48.32h45.376c26.624 0 46.912 20.928 46.912 48.32s-20.288 48.32-46.912 48.32zM134.976 267.264H89.6c-26.56 0-46.912-20.928-46.912-48.32 0-27.328 20.352-48.256 46.912-48.256h45.376c26.624 0 46.912 20.928 46.912 48.256 0 27.392-20.288 48.32-46.912 48.32zM311.744 853.312c-26.56 0-46.912-20.928-46.912-48.256 0-27.392 20.352-48.32 46.912-48.32h622.72c26.56 0 46.848 20.928 46.848 48.32 0 27.328-20.288 48.256-46.912 48.256H311.744c1.6 0 1.6 0 0 0zM311.744 560.32c-26.56 0-46.912-20.928-46.912-48.32s20.352-48.32 46.912-48.32h622.72c26.56 0 46.848 20.928 46.848 48.32s-20.288 48.32-46.912 48.32H311.744c1.6 0 1.6 0 0 0zM311.744 267.264c-26.56 0-46.912-20.928-46.912-48.32 0-27.328 20.352-48.256 46.912-48.256h622.72c26.56 0 46.848 20.928 46.848 48.256 0 27.392-20.288 48.32-46.912 48.32H311.744c1.6 0 1.6 0 0 0z" :fill "currentColor"}]]))

+ 107 - 40
src/main/frontend/extensions/pdf/highlights.cljs

@@ -250,9 +250,9 @@
                     sc-pos (pdf-utils/vw-to-scaled-pos viewer vw-pos)]
                     sc-pos (pdf-utils/vw-to-scaled-pos viewer vw-pos)]
 
 
                 ;; TODO: debug
                 ;; TODO: debug
-                (js/console.debug "[VW x SC] ====>" vw-pos sc-pos)
-                (js/console.debug "[Range] ====> [" page-info "]" (.toString sel-range) point)
-                (js/console.debug "[Rects] ====>" sel-rects " [Bounding] ====>" bounding)
+                ;;(dd "[VW x SC] ====>" vw-pos sc-pos)
+                ;;(dd "[Range] ====> [" page-info "]" (.toString sel-range) point)
+                ;;(dd "[Rects] ====>" sel-rects " [Bounding] ====>" bounding)
 
 
 
 
                 (let [hl {:id         nil
                 (let [hl {:id         nil
@@ -303,33 +303,116 @@
 
 
          (.querySelector el ".pp-holder")))
          (.querySelector el ".pp-holder")))
 
 
-     (if (seq highlights)
-       [:ul.extensions__pdf-highlights
-        (for [hl highlights]
-          [:li
-           [:a
-            {:on-click #(pdf-utils/scroll-to-highlight viewer hl)}
-            (str "#" (:id hl) "#  ")]
-           (:text (:content hl))])
-        ])]))
+     ;; debug highlights anchor
+     ;;(if (seq highlights)
+     ;;  [:ul.extensions__pdf-highlights
+     ;;   (for [hl highlights]
+     ;;     [:li
+     ;;      [:a
+     ;;       {:on-click #(pdf-utils/scroll-to-highlight viewer hl)}
+     ;;       (str "#" (:id hl) "#  ")]
+     ;;      (:text (:content hl))])
+     ;;   ])
+     ]))
+
+(rum/defc pdf-outline-item
+  [^js viewer {:keys [title items href parent dest] :as node}]
+  (let [has-child? (seq items)]
+
+    [:div.extensions__pdf-outline-item
+     {:class (if has-child? "has-children")}
+     [:div.inner
+      [:a
+       {:href      "javascript:;"
+        :data-dest (js/JSON.stringify (bean/->js dest))
+        :on-click  (fn []
+                     (when-let [^js dest (and dest (bean/->js dest))]
+                       (.goToDestination (.-linkService viewer) dest)))}
+       [:span title]]]
+
+     ;; children
+     (when has-child?
+       [:div.children
+        (map-indexed
+          (fn [idx itm]
+            (let [parent (str parent "-" idx)]
+              (rum/with-key
+                (pdf-outline-item viewer (merge itm {:parent parent})) parent))) items)])]))
+
+(rum/defc pdf-outline
+  [^js viewer hide!]
+  (when-let [^js pdf-doc (and viewer (.-pdfDocument viewer))]
+    (let [*el-outline (rum/use-ref nil)
+          [outline-data, set-outline-data!] (rum/use-state [])]
+
+      (rum/use-effect!
+        (fn []
+          (p/catch
+            (p/let [^js data (.getOutline pdf-doc)]
+              (when-let [data (and data (.map data (fn [^js it]
+                                                     (set! (.-href it) (.. viewer -linkService (getDestinationHash (.-dest it))))
+                                                     it)))])
+              (set-outline-data! (bean/->clj data)))
+
+            (fn [e]
+              (js/console.error "[Load outline Error]" e))))
+        [pdf-doc])
+
+      (rum/use-effect!
+        (fn []
+          (let [el-outline (rum/deref *el-outline)
+                cb (fn [^js e]
+                     (and (= e.which 27) (hide!)))]
+
+            (js/setTimeout #(.focus el-outline))
+            (.addEventListener el-outline "keyup" cb)
+            #(.removeEventListener el-outline "keyup" cb)))
+        [])
+
+      [:div.extensions__pdf-outline-wrap
+       {:on-click (fn [^js/MouseEvent e]
+                    (let [target (.-target e)]
+                      (when-not (.contains (rum/deref *el-outline) target)
+                        (hide!))))}
+
+       [:div.extensions__pdf-outline
+        {:ref       *el-outline
+         :tab-index -1}
+        (if (seq outline-data)
+          [:section
+           (map-indexed (fn [idx itm]
+                          (rum/with-key
+                            (pdf-outline-item viewer (merge itm {:parent idx}))
+                            idx))
+                        outline-data)]
+          [:section.is-empty "No outlines"])]])))
 
 
 (rum/defc pdf-toolbar
 (rum/defc pdf-toolbar
   [^js viewer]
   [^js viewer]
-  [:div.extensions__pdf-toolbar
-   [:div.r.flex
+  (let [[outline-visible?, set-outline-visible!] (rum/use-state false)]
+    [:div.extensions__pdf-toolbar
+     [:div.inner
+      [:div.r.flex
+
+       ;; zoom
+       [:a.button
+        {:on-click (partial pdf-utils/zoom-out-viewer viewer)}
+        (svg/zoom-out 18)]
 
 
-    ;; zoom
-    [:a.button
-     {:on-click (partial pdf-utils/zoom-out-viewer viewer)}
-     (svg/zoom-out)]
+       [:a.button
+        {:on-click (partial pdf-utils/zoom-in-viewer viewer)}
+        (svg/zoom-in 18)]
 
 
-    [:a.button
-     {:on-click (partial pdf-utils/zoom-in-viewer viewer)}
-     (svg/zoom-in)]
+       [:a.button
+        {:on-click #(set-outline-visible! (not outline-visible?))}
+        (svg/view-list 16)]
 
 
-    [:a.button
-     {:on-click #(state/set-state! :pdf/current nil)}
-     "close"]]])
+       [:a.button
+        {:on-click #(state/set-state! :pdf/current nil)}
+        "close"]]]
+
+     ;; contents outline
+     (when outline-visible? (pdf-outline viewer #(set-outline-visible! false)))]))
 
 
 (rum/defc pdf-viewer
 (rum/defc pdf-viewer
   [url initial-hls ^js pdf-document ops]
   [url initial-hls ^js pdf-document ops]
@@ -364,26 +447,14 @@
         #())
         #())
       [])
       [])
 
 
-    ;; highlights & annotations
-    (rum/use-effect!
-      (fn []
-        (js/console.debug "[rebuild loaded pages] " (:loaded-pages ano-state))
-        ;;(set-hls-state! (update-in hls-state [:dirties] inc))
-        ;; destroy
-        #())
-      [(:loaded-pages ano-state)])
-
     ;; interaction events
     ;; interaction events
     (rum/use-effect!
     (rum/use-effect!
       (fn []
       (fn []
-        (js/console.debug "[rebuild interaction events]" (:viewer state))
-
         (when-let [^js viewer (:viewer state)]
         (when-let [^js viewer (:viewer state)]
           (let [^js el (rum/deref *el-ref)
           (let [^js el (rum/deref *el-ref)
 
 
                 fn-textlayer-ready
                 fn-textlayer-ready
                 (fn [^js p]
                 (fn [^js p]
-                  (js/console.debug "text layer ready" p)
                   (set-ano-state! {:loaded-pages (conj (:loaded-pages ano-state) (int (.-pageNumber p)))}))
                   (set-ano-state! {:loaded-pages (conj (:loaded-pages ano-state) (int (.-pageNumber p)))}))
 
 
                 fn-page-ready
                 fn-page-ready
@@ -435,8 +506,6 @@
                   res (fs/read-file repo-dir hls-file)
                   res (fs/read-file repo-dir hls-file)
                   data (if res (bean/->clj (js/JSON.parse res)) [])]
                   data (if res (bean/->clj (js/JSON.parse res)) [])]
 
 
-            (dd "[initial hls] " data)
-
             (set-hls-state! {:initial-hls data}))
             (set-hls-state! {:initial-hls data}))
 
 
           ;; error
           ;; error
@@ -452,8 +521,6 @@
     (rum/use-effect!
     (rum/use-effect!
       (fn []
       (fn []
         (when-let [hls (:latest-hls hls-state)]
         (when-let [hls (:latest-hls hls-state)]
-          (dd "latest hls ===>" hls)
-
           (p/catch
           (p/catch
             (p/let [hls (if hls
             (p/let [hls (if hls
                           (js/JSON.stringify (bean/->js hls) nil 2)
                           (js/JSON.stringify (bean/->js hls) nil 2)

+ 109 - 27
src/main/frontend/extensions/pdf/pdf.css

@@ -57,20 +57,103 @@
     top: 0;
     top: 0;
     left: 0;
     left: 0;
     width: 100%;
     width: 100%;
-    height: 38px;
+    height: 42px;
     z-index: 5;
     z-index: 5;
     background: linear-gradient(0deg, rgba(255, 255, 255, 0) 3%, rgba(255, 255, 255, 1) 100%);
     background: linear-gradient(0deg, rgba(255, 255, 255, 0) 3%, rgba(255, 255, 255, 1) 100%);
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
     padding-right: 20px;
     padding-right: 20px;
 
 
-    > .r {
-      a.button {
-        user-select: none;
-        display: flex;
-        align-items: center;
-        margin-left: 8px;
+    > .inner {
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+
+      > .r {
+        a.button {
+          user-select: none;
+          display: flex;
+          align-items: center;
+          margin-left: 8px;
+        }
+      }
+    }
+  }
+
+  &-outline {
+    position: absolute;
+    right: 30px;
+    z-index: 3;
+    border-radius: 4px;
+    width: 320px;
+    overflow-y: auto;
+    background-color: #e9e9e9;
+    outline: none;
+    box-shadow: 0 2px 4px 0 rgba(134, 134, 134, 0.59);
+
+    &::after {
+      content: "";
+      position: absolute;
+      top: -50%;
+      right: -50%;
+      bottom: -50%;
+      left: -50%;
+      border: solid 1px #b2b2b2;
+      transform: scale(0.5);
+      transform-origin: center center;
+      pointer-events: none;
+      border-radius: 10px;
+    }
+
+    &-wrap {
+      position: absolute;
+      top: 40px;
+      width: 100%;
+      height: 92vh;
+      background-color: rgba(0, 0, 0, 0);
+    }
+
+    > section {
+      white-space: pre-wrap;
+      max-height: 80vh;
+      padding-top: 15px;
+      padding-bottom: 15px;
+      padding-left: 12px;
+      overflow: auto;
+      color: #565656;
+
+      > .extensions__pdf-outline-item > .inner > a {
+        font-weight: bold;
+      }
+    }
+
+    &-item {
+      > .inner {
+        > a {
+          color: #565656;
+          font-size: 11px;
+          text-decoration: none;
+          font-weight: normal;
+          display: block;
+          padding: 6px 5px;
+          user-select: none;
+          border-radius: 4px;
+          cursor: default;
+
+          margin-right: 10px;
+          transition: none;
+
+          &:hover {
+            color: #106ba3;
+          }
+
+          &:active, &:focus {
+            background-color: #106ba3;
+            color: white
+          }
+        }
+      }
+
+      > .children {
+        padding-left: 12px;
       }
       }
     }
     }
   }
   }
@@ -199,30 +282,29 @@
   transition: background 0.3s;
   transition: background 0.3s;
 
 
   background-color: rgba(252, 219, 97, 0.7);
   background-color: rgba(252, 219, 97, 0.7);
-}
 
 
-.hls-text-region-item[data-color=yellow] {
-  background-color: var(--ph-highlight-color-yellow);
-  opacity: .5;
-}
+  &[data-color=yellow] {
+    background-color: var(--ph-highlight-color-yellow);
+    opacity: .5;
+  }
 
 
-.hls-text-region-item[data-color=blue] {
-  background-color: var(--ph-highlight-color-blue);
-}
+  &[data-color=blue] {
+    background-color: var(--ph-highlight-color-blue);
+  }
 
 
-.hls-text-region-item[data-color=green] {
-  background-color: var(--ph-highlight-color-green);
-}
+  &[data-color=green] {
+    background-color: var(--ph-highlight-color-green);
+  }
 
 
-.hls-text-region-item[data-color=red] {
-  background-color: var(--ph-highlight-color-red);
-}
+  &[data-color=red] {
+    background-color: var(--ph-highlight-color-red);
+  }
 
 
-.hls-text-region-item[data-color=purple] {
-  background-color: var(--ph-highlight-color-purple);
+  &[data-color=purple] {
+    background-color: var(--ph-highlight-color-purple);
+  }
 }
 }
 
 
-
 body[data-page] {
 body[data-page] {
   #main-content-container {
   #main-content-container {
     margin: 0 !important;
     margin: 0 !important;