pipipi-pikachu 5 سال پیش
والد
کامیت
2d245dd88b

+ 33 - 0
src/hooks/useDropImage.ts

@@ -0,0 +1,33 @@
+import { ref, onMounted, onUnmounted, Ref } from 'vue'
+
+export default (elementRef: Ref<HTMLElement | null>) => {
+  const imageFile = ref<File | null>(null)
+
+  const handleDrop = (e: DragEvent) => {
+    if(!e.dataTransfer) return
+    const file = e.dataTransfer.items[0]
+    if( file.kind === 'file' && file.type.indexOf('image') !== -1 ) {
+      const _imageFile = file.getAsFile()
+      if(_imageFile) imageFile.value = _imageFile
+    }
+  }
+
+  onMounted(() => {
+    elementRef.value && elementRef.value.addEventListener('drop', handleDrop)
+
+    document.ondragleave = e => e.preventDefault()
+    document.ondrop = e => e.preventDefault()
+    document.ondragenter = e => e.preventDefault()
+    document.ondragover = e => e.preventDefault()
+  })
+  onUnmounted(() => {
+    elementRef.value && elementRef.value.removeEventListener('drop', handleDrop)
+
+    document.ondragleave = null
+    document.ondrop = null
+    document.ondragenter = null
+    document.ondragover = null
+  })
+
+  return imageFile
+}

+ 11 - 7
src/views/Editor/Canvas/index.vue

@@ -40,13 +40,15 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue'
+import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
 import { useStore } from 'vuex'
 import { State } from '@/store/state'
 import { MutationTypes } from '@/store/constants'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
 
+import useDropImage from '@/hooks/useDropImage'
+
 import MouseSelection from './MouseSelection.vue'
 import SlideBackground from './SlideBackground.vue'
 import AlignmentLine, { AlignmentLineProps } from './AlignmentLine.vue'
@@ -59,9 +61,15 @@ export default defineComponent({
     AlignmentLine,
   },
   setup() {
+    const viewportRef = ref<HTMLElement | null>(null)
     const isShowGridLines = ref(false)
     const alignmentLines = ref<AlignmentLineProps[]>([])
 
+    const dropImageFile = useDropImage(viewportRef)
+    watch(dropImageFile, () => {
+      console.log(dropImageFile.value)
+    })
+
     const viewportStyles = reactive({
       width: VIEWPORT_SIZE,
       height: VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO,
@@ -99,14 +107,14 @@ export default defineComponent({
     }
 
     const resizeObserver = new ResizeObserver(setViewportSize)
+
     onMounted(() => {
       if(canvasRef.value) resizeObserver.observe(canvasRef.value)
     })
     onUnmounted(() => {
       if(canvasRef.value) resizeObserver.unobserve(canvasRef.value)
     })
-
-    const viewportRef = ref<Element | null>(null)
+    
     const mouseSelectionState = reactive({
       isShow: false,
       top: 0,
@@ -213,10 +221,6 @@ export default defineComponent({
           ],
         },
         {
-          text: '背景设置',
-        },
-        { divider: true },
-        {
           text: '清空页面',
         },
       ]

+ 22 - 0
src/views/Editor/Canvas/utils/alignLines.ts

@@ -0,0 +1,22 @@
+interface AlignLine {
+  value: number;
+  range: [number, number];
+}
+
+// 对齐参考线去重,对于相同位置的多条参考线,取长度范围的最小值和最大值,并基于此范围将多条参考线合并为一条
+export const uniqAlignLines = (lines: AlignLine[]) => {
+  const uniqLines: AlignLine[] = []
+  lines.forEach(line => {
+    const index = uniqLines.findIndex(_line => _line.value === line.value)
+    if(index === -1) uniqLines.push(line)
+    else {
+      const uniqLine = uniqLines[index]
+      const rangeMin = Math.min(uniqLine.range[0], line.range[0])
+      const rangeMax = Math.max(uniqLine.range[1], line.range[1])
+      const range: [number, number] = [rangeMin, rangeMax]
+      const _line = { value: line.value, range }
+      uniqLines[index] = _line
+    }
+  })
+  return uniqLines
+}

+ 85 - 0
src/views/Editor/Canvas/utils/elementRange.ts

@@ -0,0 +1,85 @@
+import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement, PPTIconElement } from '@/types/slides'
+
+// 获取矩形旋转后在画布中的位置范围
+export const getRectRotatedRange = (element: PPTTextElement | PPTImageElement | PPTShapeElement | PPTIconElement) => {
+  const { left, top, width, height, rotate = 0 } = element
+
+  const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2
+  const auxiliaryAngle = Math.atan(height / width) * 180 / Math.PI
+
+  const tlbraRadian = (180 - rotate - auxiliaryAngle) * Math.PI / 180
+  const trblaRadian = (auxiliaryAngle - rotate) * Math.PI / 180
+
+  const halfWidth = width / 2
+  const halfHeight = height / 2
+
+  const middleLeft = left + halfWidth
+  const middleTop = top + halfHeight
+
+  const xAxis = [
+    middleLeft + radius * Math.cos(tlbraRadian),
+    middleLeft + radius * Math.cos(trblaRadian),
+    middleLeft - radius * Math.cos(tlbraRadian),
+    middleLeft - radius * Math.cos(trblaRadian),
+  ]
+  const yAxis = [
+    middleTop - radius * Math.sin(tlbraRadian),
+    middleTop - radius * Math.sin(trblaRadian),
+    middleTop + radius * Math.sin(tlbraRadian),
+    middleTop + radius * Math.sin(trblaRadian),
+  ]
+
+  return {
+    xRange: [Math.min(...xAxis), Math.max(...xAxis)],
+    yRange: [Math.min(...yAxis), Math.max(...yAxis)],
+  }
+}
+
+// 获取元素在画布中的位置范围
+export const getElementRange = (element: PPTElement) => {
+  let minX, maxX, minY, maxY
+
+  if(element.type === 'line') {
+    minX = element.left
+    maxX = element.left + Math.max(element.start[0], element.end[0])
+    minY = element.top
+    maxY = element.top + Math.max(element.start[1], element.end[1])
+  }
+  else if('rotate' in element && element.rotate) {
+    const { xRange, yRange } = getRectRotatedRange(element)
+    minX = xRange[0]
+    maxX = xRange[1]
+    minY = yRange[0]
+    maxY = yRange[1]
+  }
+  else {
+    minX = element.left
+    maxX = element.left + element.width
+    minY = element.top
+    maxY = element.top + element.height
+  }
+  return { minX, maxX, minY, maxY }
+}
+
+// 获取元素集合在画布中的位置范围
+export const getElementListRange = (elementList: PPTElement[]) => {
+  const leftValues: number[] = []
+  const topValues: number[] = []
+  const rightValues: number[] = []
+  const bottomValues: number[] = []
+
+  elementList.forEach(element => {
+    const { minX, maxX, minY, maxY } = getElementRange(element)
+    leftValues.push(minX)
+    topValues.push(minY)
+    rightValues.push(maxX)
+    bottomValues.push(maxY)
+  })
+
+  const minX = Math.min(...leftValues)
+  const maxX = Math.max(...rightValues)
+  const minY = Math.min(...topValues)
+  const maxY = Math.max(...bottomValues)
+
+  return { minX, maxX, minY, maxY }
+}

+ 80 - 0
src/views/Editor/Canvas/utils/elementRotate.ts

@@ -0,0 +1,80 @@
+import { PPTTextElement, PPTImageElement, PPTShapeElement, PPTIconElement } from '@/types/slides'
+import { OPERATE_KEYS } from '@/configs/element'
+
+// 给定一个坐标,计算该坐标到(0, 0)点连线的弧度值
+// 注意,Math.atan2的一般用法是Math.atan2(y, x)返回的是原点(0,0)到(x,y)点的线段与X轴正方向之间的弧度值
+// 这里将使用时将x与y的传入顺序交换了,为的是获取原点(0,0)到(x,y)点的线段与Y轴正方向之间的弧度值
+export const getAngleFromCoordinate = (x: number, y: number) => {
+  const radian = Math.atan2(x, y)
+  const angle = 180 / Math.PI * radian
+  return angle
+}
+
+// 计算元素被旋转一定角度后,八个操作点的新坐标
+export const getRotateElementPoints = (element: PPTTextElement | PPTImageElement | PPTShapeElement | PPTIconElement, angle: number) => {
+  const { left, top, width, height } = element
+
+  const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2
+  const auxiliaryAngle = Math.atan(height / width) * 180 / Math.PI
+
+  const tlbraRadian = (180 - angle - auxiliaryAngle) * Math.PI / 180
+  const trblaRadian = (auxiliaryAngle - angle) * Math.PI / 180
+  const taRadian = (90 - angle) * Math.PI / 180
+  const raRadian = angle * Math.PI / 180
+
+  const halfWidth = width / 2
+  const halfHeight = height / 2
+
+  const middleLeft = left + halfWidth
+  const middleTop = top + halfHeight
+
+  const leftTopPoint = {
+    left: middleLeft + radius * Math.cos(tlbraRadian),
+    top: middleTop - radius * Math.sin(tlbraRadian),
+  }
+  const topPoint = {
+    left: middleLeft + halfHeight * Math.cos(taRadian),
+    top: middleTop - halfHeight * Math.sin(taRadian),
+  }
+  const rightTopPoint = {
+    left: middleLeft + radius * Math.cos(trblaRadian),
+    top: middleTop - radius * Math.sin(trblaRadian),
+  }
+  const rightPoint = {
+    left: middleLeft + halfWidth * Math.cos(raRadian),
+    top: middleTop + halfWidth * Math.sin(raRadian),
+  }
+  const rightBottomPoint = {
+    left: middleLeft - radius * Math.cos(tlbraRadian),
+    top: middleTop + radius * Math.sin(tlbraRadian),
+  }
+  const bottomPoint = {
+    left: middleLeft - halfHeight * Math.sin(raRadian),
+    top: middleTop + halfHeight * Math.cos(raRadian),
+  }
+  const leftBottomPoint = {
+    left: middleLeft - radius * Math.cos(trblaRadian),
+    top: middleTop + radius * Math.sin(trblaRadian),
+  }
+  const leftPoint = {
+    left: middleLeft - halfWidth * Math.cos(raRadian),
+    top: middleTop - halfWidth * Math.sin(raRadian),
+  }
+
+  return { leftTopPoint, topPoint, rightTopPoint, rightPoint, rightBottomPoint, bottomPoint, leftBottomPoint, leftPoint }
+}
+
+// 获取元素某个操作点对角线上另一端的操作点坐标(例如:左上 <-> 右下)
+export const getOppositePoint = (direction: number, points: ReturnType<typeof getRotateElementPoints>) => {
+  const oppositeMap = {
+    [OPERATE_KEYS.RIGHT_BOTTOM]: points.leftTopPoint,
+    [OPERATE_KEYS.LEFT_BOTTOM]: points.rightTopPoint,
+    [OPERATE_KEYS.LEFT_TOP]: points.rightBottomPoint,
+    [OPERATE_KEYS.RIGHT_TOP]: points.leftBottomPoint,
+    [OPERATE_KEYS.TOP]: points.bottomPoint,
+    [OPERATE_KEYS.BOTTOM]: points.topPoint,
+    [OPERATE_KEYS.LEFT]: points.rightPoint,
+    [OPERATE_KEYS.RIGHT]: points.leftPoint,
+  }
+  return oppositeMap[direction]
+}

+ 70 - 19
src/views/Editor/index.vue

@@ -17,6 +17,7 @@ import { computed, defineComponent, onMounted, onUnmounted, ref } from 'vue'
 import { useStore } from 'vuex'
 import { State } from '@/store/state'
 import { KEYCODE } from '@/configs/keyCode'
+import { decrypt } from '@/utils/index'
 
 import { message } from 'ant-design-vue'
 
@@ -87,25 +88,68 @@ export default defineComponent({
       if(ctrlKey && !ctrlKeyDown.value) ctrlKeyDown.value = true
       if(shiftKey && !shiftKeyDown.value) shiftKeyDown.value = true
       
-      if(!editorAreaFocus.value && !thumbnailsFocus.value) return
-
-      e.preventDefault()
+      if(!editorAreaFocus.value && !thumbnailsFocus.value) return      
       
-      if(ctrlKey && keyCode === KEYCODE.S) save()
-      if(ctrlKey && keyCode === KEYCODE.C) copy()
-      if(ctrlKey && keyCode === KEYCODE.X) cut()
-      if(ctrlKey && keyCode === KEYCODE.Z) undo()
-      if(ctrlKey && keyCode === KEYCODE.Y) redo()
-      if(ctrlKey && keyCode === KEYCODE.A) selectAll()
-      if(ctrlKey && keyCode === KEYCODE.L) lock()
-      if(!shiftKey && ctrlKey && keyCode === KEYCODE.G) combine()
-      if(shiftKey && ctrlKey && keyCode === KEYCODE.G) uncombine()
-      if(keyCode === KEYCODE.DELETE) remove()
-      if(keyCode === KEYCODE.UP) move(KEYCODE.UP)
-      if(keyCode === KEYCODE.DOWN) move(KEYCODE.DOWN)
-      if(keyCode === KEYCODE.LEFT) move(KEYCODE.LEFT)
-      if(keyCode === KEYCODE.RIGHT) move(KEYCODE.RIGHT)
-      if(keyCode === KEYCODE.ENTER) create()
+      if(ctrlKey && keyCode === KEYCODE.S) {
+        e.preventDefault()
+        save()
+      }
+      if(ctrlKey && keyCode === KEYCODE.C) {
+        e.preventDefault()
+        copy()
+      }
+      if(ctrlKey && keyCode === KEYCODE.X) {
+        e.preventDefault()
+        cut()
+      }
+      if(ctrlKey && keyCode === KEYCODE.Z) {
+        e.preventDefault()
+        undo()
+      }
+      if(ctrlKey && keyCode === KEYCODE.Y) {
+        e.preventDefault()
+        redo()
+      }
+      if(ctrlKey && keyCode === KEYCODE.A) {
+        e.preventDefault()
+        selectAll()
+      }
+      if(ctrlKey && keyCode === KEYCODE.L) {
+        e.preventDefault()
+        lock()
+      }
+      if(!shiftKey && ctrlKey && keyCode === KEYCODE.G) {
+        e.preventDefault()
+        combine()
+      }
+      if(shiftKey && ctrlKey && keyCode === KEYCODE.G) {
+        e.preventDefault()
+        uncombine()
+      }
+      if(keyCode === KEYCODE.DELETE) {
+        e.preventDefault()
+        remove()
+      }
+      if(keyCode === KEYCODE.UP) {
+        e.preventDefault()
+        move(KEYCODE.UP)
+      }
+      if(keyCode === KEYCODE.DOWN) {
+        e.preventDefault()
+        move(KEYCODE.DOWN)
+      }
+      if(keyCode === KEYCODE.LEFT) {
+        e.preventDefault()
+        move(KEYCODE.LEFT)
+      }
+      if(keyCode === KEYCODE.RIGHT) {
+        e.preventDefault()
+        move(KEYCODE.RIGHT)
+      }
+      if(keyCode === KEYCODE.ENTER) {
+        e.preventDefault()
+        create()
+      }
     }
     
     const keyupListener = () => {
@@ -118,7 +162,14 @@ export default defineComponent({
     }
 
     const pasteText = (text: string) => {
-      console.log(text)
+      let content
+      try {
+        content = JSON.parse(decrypt(text))
+      }
+      catch {
+        content = text
+      }
+      console.log(content)
     }
 
     const pasteListener = (e: ClipboardEvent) => {