Browse Source

update: 注释面板UI

wzl 1 year ago
parent
commit
e045aba9b0

+ 304 - 61
packages/webview/src/components/AnnotationContainer/AnnotationContent.vue

@@ -7,24 +7,76 @@
             <span>{{ $t('leftPanel.page') }} {{ pageNumber * 1 + 1 }}</span>
             <span>{{ pageAnnotations.pageAnnotationsCount }}</span>
           </div>
-          <template v-for="(item) in pageAnnotations.annotations">
-            <div v-if="!item.isDelete && item.type !== 'link'" class="annotation-item" @click="goToPage(item.pageIndex)">
+          <template v-for="(item, index) in pageAnnotations.annotations">
+            <div v-if="!item.isDelete && item.type !== 'link'" class="annotation-item" @click="goToPage(item.pageIndex, index)" :class="{ 'selected': selectedItemIndex === `${item.pageIndex}-${index}` }">
               <div class="item-header">
                 <Highlight v-if="item.type === 'highlight'" />
                 <Squiggle v-else-if="item.type === 'squiggly'" />
                 <Strikeout v-else-if="item.type === 'strikeout'" />
                 <Underline v-else-if="item.type === 'underline'" />
                 <Ink v-else-if="item.type === 'ink'" />
-                <LineTool v-else-if="item.type === 'line' && (((item.tail === 'None' || item.tail === 'Unknown') && (item.head === 'None' || item.head === 'Unknown')) || (!item.tail && !item.head))" />
-                <ArrowTool v-else-if="item.type === 'line' && (item.tail === 'OpenArrow' || item.head === 'OpenArrow' || item.arrow)" />
+                <LineTool
+                  v-else-if="item.type === 'line' && (((item.tail === 'None' || item.tail === 'Unknown') && (item.head === 'None' || item.head === 'Unknown')) || (!item.tail && !item.head))" />
+                <ArrowTool
+                  v-else-if="item.type === 'line' && (item.tail === 'OpenArrow' || item.head === 'OpenArrow' || item.arrow)" />
                 <RectangleTool v-else-if="item.type === 'square'" />
                 <EllipseTool v-else-if="item.type === 'circle'" />
                 <Text v-else-if="item.type === 'freetext'" />
                 <Note v-else-if="item.type === 'text'" />
                 <Stamp v-else-if="item.type === 'image' || item.type === 'stamp'" />
-                <span>{{ dayjs(item.date).format('DD/MM/YYYY HH:mm:ss') }}</span>
+
+                <div>
+                  <p class="name">Guest</p>
+                  <p class="date">{{ dayjs(item.date).format('DD/MM/YYYY HH:mm:ss') }}</p>
+                  <div v-if="item.contents" class="item-content">{{ item.contents }}</div>
+                </div>
+                <n-popover trigger="hover" placement="bottom" class="mark-popover">
+                  <template #trigger>
+                    <div class="mark-box" :class="{ 'marked': item.marked }" @click="item.marked = !item.marked"></div>
+                  </template>
+                  <span>Marked</span>
+                </n-popover>
+              </div>
+
+              <div class="item-reply" v-if="selectedItemIndex === `${item.pageIndex}-${index}`">
+                <input type="text" placeholder="Reply or add thoghts" v-model="replyText">
+                <div>
+                  <span @click="cancel">Cancel</span>
+                  <button :class="{ 'active': replyText }">Reply</button>
+                </div>
+              </div>
+
+              <div class="item-footer" v-if="selectedItemIndex === `${item.pageIndex}-${index}`">
+                <n-popover
+                  ref="popover"
+                  placement="bottom"
+                  trigger="click"
+                  :show-arrow="false"
+                  to="#outerContainer"
+                  :raw="true"
+                  :z-index="96"
+                  class="state-popover"
+                  :show="showPopover"
+                  @update:show="handleUpdateShow"
+                >
+                  <template #trigger>
+                    <Button :class="{ active: showPopover }">
+                      <Accepted />
+                      <!-- <component :is="state" /> -->
+                    </Button>
+                  </template>
+                  <div class="drop-down">
+                    <div class="drop-item" @click="setState('Accepted')"><Accepted />Accepted</div>
+                    <div class="drop-item" @click="setState('Rejected')"><Rejected />Rejected</div>
+                    <div class="drop-item" @click="setState('Cancelled')"><Cancelled />Cancelled</div>
+                  </div>
+                </n-popover>
+                <div class="re">
+                  <Reply />
+                  <span>4</span>
+                  <Return />
+                </div>
               </div>
-              <div v-if="item.contents" class="item-content">{{ item.contents }}</div>
             </div>
           </template>
         </template>
@@ -35,82 +87,273 @@
 </template>
 
 <script setup>
-  import { computed, ref, getCurrentInstance } from 'vue';
-  import dayjs from 'dayjs'
-  import core from '@/core'
-  import { useDocumentStore } from '@/stores/modules/document'
+import { computed, ref, getCurrentInstance, watch } from 'vue'
+import dayjs from 'dayjs'
+import core from '@/core'
+import { useDocumentStore } from '@/stores/modules/document'
+import { useViewerStore } from '@/stores/modules/viewer'
+import { NPopover } from 'naive-ui'
 
-  const useDocument = useDocumentStore()
+const useDocument = useDocumentStore()
+const useViewer = useViewerStore()
 
-  let annotationsContainers = computed(() => useDocument.getAllAnnotations)
+let annotationsContainers = computed(() => useDocument.getAllAnnotations)
+const activePanelTab = computed(() => useViewer.getActiveElementTab('leftPanelTab'))
+const isOpen = computed(() => useViewer.isElementOpen('leftPanel'))
+// const showPopover = computed(() => popover.value && popover.value.getMergedShow())
 
-  const markup = ['highlight', 'underline', 'squiggly', 'strikeout']
+const markup = ['highlight', 'underline', 'squiggly', 'strikeout']
+const replyText = ref('')
+const selectedItemIndex = ref('')
+const popover = ref(null)
+const showPopover = ref(false)
+const state = ref('Accepted')
 
-  const setAnnotationList = ({ annotations }) => {
-    useDocument.initAnnotations(annotations)
-    const instance = getCurrentInstance();
-    instance?.proxy?.$forceUpdate();
-  }
-  core.addEvent('annotationChanged', setAnnotationList)
+watch([activePanelTab, isOpen], (oldValue, newValue) => {
+  if (newValue[0] === 'ANNOTATION' || newValue[1]) cancel()
+})
+
+const setAnnotationList = ({ annotations }) => {
+  useDocument.initAnnotations(annotations)
+  const instance = getCurrentInstance();
+  instance?.proxy?.$forceUpdate();
+}
+core.addEvent('annotationChanged', setAnnotationList)
+
+const goToPage = (page, index) => {
+  core.pageNumberChanged({
+    value: (page * 1 + 1).toString()
+  })
+  selectedItemIndex.value = page + '-' + index
+}
+
+const cancel = (e) => {
+  e?.stopPropagation()
+  selectedItemIndex.value = ''
+  replyText.value = ''
+}
+
+const setState = (val) => {
+  state.value = val
+  showPopover.value = false
+}
+
+const handleUpdateShow = () => {
+  showPopover.value = !showPopover.value
+}
 
-  const goToPage = (page) => {
-    core.pageNumberChanged({
-      value: (page * 1 + 1).toString()
-    })
-  }
 </script>
 
 <style lang="scss">
-  .annotation-view {
-    position: relative;
-    height: calc(100% - 95px);
-    overflow: auto;
-    .page-title {
+.annotation-view {
+  position: relative;
+  padding: 0 8px;
+  height: calc(100% - 95px);
+  overflow: auto;
+
+  .page-title {
+    display: flex;
+    justify-content: space-between;
+    padding: 6px 8px;
+    color: var(--c-side-title);
+
+    span {
+      font-size: 14px;
+      line-height: 20px;
+    }
+  }
+
+  .annotation-item {
+    margin-top: 4px;
+    padding: 4px 8px;
+    background: var(--c-side-annotation-bg);
+    border-radius: 4px;
+    border: 1.2px solid transparent;
+
+    & + .annotation-item {
+      margin-top: 8px;
+    }
+
+    &.selected {
+      border: 1.2px solid var(--c-findbar-input-border);
+    }
+
+    .item-header {
+      position: relative;
       display: flex;
-      justify-content: space-between;
-      padding: 6px 16px;
-      color: var(--c-side-title);
-      background-color: var(--c-side-annotation-bg);
-      span {
-        font-size: 14px;
-        line-height: 20px;
+      padding: 8px 22px 8px 0;
+      color: var(--c-side-text);
+
+      svg {
+        margin-top: 6px;
+        margin-right: 8px;
+        min-width: 20px;
       }
-    }
-    .annotation-item {
-      padding: 4px 16px 12px;
-      .item-header {
-        display: flex;
-        align-items: center;
-        padding: 6px 0;
-        color: var(--c-side-text);
-        span {
-          margin-left: 8px;
-          font-size: 14px;
-          line-height: 20px;
-        }
+
+      .name {
+        font-size: 12px;
+        font-weight: 700;
+        line-height: 16px;
+      }
+
+      .date {
+        font-size: 10px;
+        line-height: 16px;
+        color: #999;
       }
+
       .item-content {
+        padding-top: 4px;
         font-size: 14px;
-        line-height: 20px;
+        line-height: 16px;
         text-overflow: ellipsis;
         display: -webkit-box;
         -webkit-box-orient: vertical;
-        -webkit-line-clamp: 2;
+        -webkit-line-clamp: 4;
         overflow: hidden;
         color: var(--c-side-annotation-text);
         word-break: break-all;
       }
+
+      .mark-box {
+        position: absolute;
+        top: 17px;
+        right: 0;
+        width: 14px;
+        height: 14px;
+        border: 1.2px solid #D9D9D9;
+        border-radius: 2px;
+        background: #fff;
+        cursor: pointer;
+
+        &.marked {
+          border-color: #999;
+          background: none;
+
+          &::before {
+            content: '';
+            width: 6px;
+            height: 6px;
+            background: #999;
+            border-radius: 50%;
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+          }
+        }
+      }
     }
-    .no-annotations {
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      transform: translate(-50%, 50%);
-      color: var(--c-side-text);
-      text-align: center;
-      font-weight: 700;
-      font-size: 14px;
-      line-height: 16px;
+
+    .item-reply {
+      padding: 8px 0 8px 28px;
+      
+      input {
+        padding: 4px 4px 4px 8px;
+        width: 100%;
+        height: 24px;
+        background: var(--c-right-side-content-fillbox-bg);
+        border: 1px solid var(--c-right-side-content-fillbox-border);
+        border-radius: 1px;
+        font-size: 14px;
+        line-height: 16px;
+        color: var(--c-text);
+
+        &::placeholder {
+          color: #999;
+        }
+      }
+
+      > div {
+        margin-top: 4px;
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+
+        span {
+          margin-right: 8px;
+          font-size: 12px;
+          line-height: 16px;
+          color: #999;
+          cursor: pointer;
+        }
+
+        button {
+          padding: 2px 6.5px;
+          border: none;
+          border-radius: 2px;
+          font-size: 12px;
+          line-height: 16px;
+          color: #999;
+          background: #D9D9D9;
+
+          &.active {
+            cursor: pointer;
+            color: #fff;
+            background: #1460F3;
+          }
+        }
+      }
+    }
+
+    .item-footer {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      color: var(--c-side-annotation-text);
+
+      button {
+        color: var(--c-side-annotation-text);
+      }
+
+      .re {
+        display: flex;
+        align-items: center;
+
+        span {
+          margin-left: 4px;
+        }
+
+        :last-child {
+          margin-left: 12px;
+        }
+      }
     }
   }
+
+  .no-annotations {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, 50%);
+    color: var(--c-side-text);
+    text-align: center;
+    font-weight: 700;
+    font-size: 14px;
+    line-height: 16px;
+  }
+}
+
+.state-popover .drop-down .drop-item {
+  padding: 4px 8px;
+
+  &:hover {
+    color: var(--c-text);
+    background-color: var(--c-header-button-active);
+  }
+
+  svg {
+    margin-right: 8px;
+  }
+}
+
+.mark-popover {
+  padding: 4px !important;
+  color: #fff;
+  background: #414960 !important;
+
+  .n-popover-arrow {
+    background-color: #414960 !important;
+  }
+}
 </style>

+ 17 - 10
packages/webview/src/components/AnnotationContainer/AnnotationHeader.vue

@@ -1,14 +1,7 @@
 <template>
   <div class="annotation-header">
-    <h2>{{ $t('leftPanel.annotations') }}</h2>
+    <h2 class="annotation-title">{{ $t('leftPanel.annotations') }}</h2>
     <div class="annotation-header-buttons">
-      <Button
-        className="button-icon"
-        img="icon-export"
-        :onClick="onImport"
-        :title="$t('leftPanel.importAnnotations')"
-        :disabled="totalPages <= 0"
-      ><Export /></Button>
       <Button
         className="button-icon"
         img="icon-import"
@@ -16,6 +9,13 @@
         :title="$t('leftPanel.exportAnnotations')"
         :disabled="!annotations || annotations.annotationsCount === 0"
       ><Import /></Button>
+      <Button
+        className="button-icon"
+        img="icon-export"
+        :onClick="onImport"
+        :title="$t('leftPanel.importAnnotations')"
+        :disabled="totalPages <= 0"
+      ><Export /></Button>
       <!-- <Button
         className="button-icon"
         img="icon-delete"
@@ -126,15 +126,22 @@ function download(blobUrl, filename) {
     align-items: center;
     justify-content: space-between;
     padding: 6px 16px;
-    h2 {
+
+    .annotation-title {
       padding: 0;
     }
+
     .annotation-header-buttons {
       display: flex;
       align-items: center;
+
       button {
-        margin-left: 8px;
+        padding: 0;
         margin-right: 0;
+
+        & + button {
+          margin-left: 8px;
+        }
       }
     }
   }

+ 1 - 1
packages/webview/src/components/DocumentContainer/DocumentContainer.vue

@@ -192,7 +192,7 @@ onMounted(async () => {
     toggleButton: document.querySelector('.toggle-button')
   })
   let initialDoc = getHashParameters('d', '')
-  initialDoc = initialDoc ? JSON.parse(initialDoc) : './example/PDF Tech Admin Console Guidelines.pdf'
+  initialDoc = initialDoc ? JSON.parse(initialDoc) : './example/ComPDFKit for Web example.pdf'
   initialDoc = Array.isArray(initialDoc) ? initialDoc : [initialDoc]
   const activeTab = useViewer.activeTab || 0
   initialDoc = initialDoc[activeTab]

File diff suppressed because it is too large
+ 5 - 0
packages/webview/src/components/Icon/Accepted.vue


+ 5 - 0
packages/webview/src/components/Icon/Cancelled.vue

@@ -0,0 +1,5 @@
+<template>
+  <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M4 4H15.5V15.5H4V4ZM2.5 2.5H4H15.5H17V4V15.5V17H15.5H4H2.5V15.5V4V2.5ZM7.06066 6L7.59099 6.53033L9.56066 8.5L11.5303 6.53033L12.0607 6L13.1213 7.06066L12.591 7.59099L10.6213 9.56066L12.591 11.5303L13.1213 12.0607L12.0607 13.1213L11.5303 12.591L9.56066 10.6213L7.59099 12.591L7.06066 13.1213L6 12.0607L6.53033 11.5303L8.5 9.56066L6.53033 7.59099L6 7.06066L7.06066 6Z" fill="currentColor"/>
+  </svg>
+</template>

+ 5 - 0
packages/webview/src/components/Icon/Completed.vue

@@ -0,0 +1,5 @@
+<template>
+  <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M4 4H15.5V15.5H4V4ZM2.5 2.5H4H15.5H17V4V15.5V17H15.5H4H2.5V15.5V4V2.5ZM13.6441 8.40148L14.1745 7.87115L13.1138 6.81049L12.5835 7.34082L8.87115 11.0531L7.53033 9.71231L7 9.18198L5.93934 10.2426L6.46967 10.773L8.34082 12.6441L8.87115 13.1745L9.40148 12.6441L13.6441 8.40148Z" fill="currentColor"/>
+  </svg>
+</template>

+ 5 - 0
packages/webview/src/components/Icon/None.vue

@@ -0,0 +1,5 @@
+<template>
+  <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M4 4H15.5V15.5H4V4ZM2.5 2.5H4H15.5H17V4V15.5V17H15.5H4H2.5V15.5V4V2.5ZM7 9.25H6.25V10.75H7H13H13.75V9.25H13H7Z" fill="currentColor"/>
+  </svg>
+</template>

File diff suppressed because it is too large
+ 5 - 0
packages/webview/src/components/Icon/Rejected.vue


+ 5 - 0
packages/webview/src/components/Icon/Reply.vue

@@ -0,0 +1,5 @@
+<template>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path d="M12.8 9.86666C12.8 10.1613 12.683 10.444 12.4746 10.6523C12.2662 10.8607 11.9836 10.9778 11.6889 10.9778H5.02227L2.80005 13.2V4.31111C2.80005 4.01642 2.91711 3.73381 3.12549 3.52543C3.33386 3.31706 3.61647 3.2 3.91116 3.2H11.6889C11.9836 3.2 12.2662 3.31706 12.4746 3.52543C12.683 3.73381 12.8 4.01642 12.8 4.31111V9.86666Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+  </svg>
+</template>

+ 6 - 0
packages/webview/src/components/Icon/Return.vue

@@ -0,0 +1,6 @@
+<template>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path d="M6.36661 7L3.19995 10.1667L6.36661 13.3333" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+    <path d="M13.3333 3.2V7.63332C13.3333 8.3052 13.0664 8.94957 12.5913 9.42466C12.1162 9.89975 11.4718 10.1667 10.7999 10.1667H3.19995" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+  </svg>
+</template>

+ 1 - 1
packages/webview/src/components/ZoomOverlay/ZoomOverlay.vue

@@ -12,7 +12,7 @@
           :show-arrow="false"
           to="#outerContainer"
           :raw="true"
-          :z-index="72"
+          :z-index="96"
           :disabled="!load"
         >
           <template #trigger>