pipipi-pikachu преди 5 години
родител
ревизия
5f20da11b3

+ 151 - 197
package-lock.json

@@ -2258,6 +2258,122 @@
         "tslint": "^5.20.1",
         "webpack": "^4.0.0",
         "yorkie": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792369066&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
+          "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1591687000046&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
+          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz?cache=0&sync_timestamp=1566248870121&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcolor-convert%2Fdownload%2Fcolor-convert-2.0.1.tgz",
+          "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
+          "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
+          "dev": true,
+          "optional": true
+        },
+        "fork-ts-checker-webpack-plugin-v5": {
+          "version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
+          "resolved": "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-5.2.1.tgz",
+          "integrity": "sha1-eTJthpeXkG+osk4qvPlCH8gFRQ0=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "@babel/code-frame": "^7.8.3",
+            "@types/json-schema": "^7.0.5",
+            "chalk": "^4.1.0",
+            "cosmiconfig": "^6.0.0",
+            "deepmerge": "^4.2.2",
+            "fs-extra": "^9.0.0",
+            "memfs": "^3.1.2",
+            "minimatch": "^3.0.4",
+            "schema-utils": "2.7.0",
+            "semver": "^7.3.2",
+            "tapable": "^1.0.0"
+          }
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz",
+          "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
+          "dev": true,
+          "optional": true
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz?cache=0&sync_timestamp=1594427484405&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-6.0.0.tgz",
+          "integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "schema-utils": {
+          "version": "2.7.0",
+          "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.7.0.tgz",
+          "integrity": "sha1-FxUfdtjq5n+793lgwzxnatn078c=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "@types/json-schema": "^7.0.4",
+            "ajv": "^6.12.2",
+            "ajv-keywords": "^3.4.1"
+          }
+        },
+        "semver": {
+          "version": "7.3.4",
+          "resolved": "https://registry.npm.taobao.org/semver/download/semver-7.3.4.tgz?cache=0&sync_timestamp=1606852064928&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.3.4.tgz",
+          "integrity": "sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1608033330722&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
+          "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
+          "integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
+          "dev": true,
+          "optional": true
+        }
       }
     },
     "@vue/cli-plugin-unit-jest": {
@@ -2421,6 +2537,17 @@
             "unique-filename": "^1.1.1"
           }
         },
+        "chalk": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1591687000046&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
+          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
         "cliui": {
           "version": "6.0.0",
           "resolved": "https://registry.npm.taobao.org/cliui/download/cliui-6.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcliui%2Fdownload%2Fcliui-6.0.0.tgz",
@@ -2483,6 +2610,18 @@
             "graceful-fs": "^4.1.6"
           }
         },
+        "loader-utils": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
+          "integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
@@ -2531,6 +2670,18 @@
           "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=",
           "dev": true
         },
+        "vue-loader-v16": {
+          "version": "npm:vue-loader@16.1.2",
+          "resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.2.tgz?cache=0&sync_timestamp=1608187974157&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.2.tgz",
+          "integrity": "sha1-XAO2xQ0qX5g8fOuhXFDXjKKymPQ=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "chalk": "^4.1.0",
+            "hash-sum": "^2.0.0",
+            "loader-utils": "^2.0.0"
+          }
+        },
         "wrap-ansi": {
           "version": "6.2.0",
           "resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-6.2.0.tgz",
@@ -7322,122 +7473,6 @@
         "worker-rpc": "^0.1.0"
       }
     },
-    "fork-ts-checker-webpack-plugin-v5": {
-      "version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
-      "resolved": "https://registry.npm.taobao.org/fork-ts-checker-webpack-plugin/download/fork-ts-checker-webpack-plugin-5.2.1.tgz",
-      "integrity": "sha1-eTJthpeXkG+osk4qvPlCH8gFRQ0=",
-      "dev": true,
-      "optional": true,
-      "requires": {
-        "@babel/code-frame": "^7.8.3",
-        "@types/json-schema": "^7.0.5",
-        "chalk": "^4.1.0",
-        "cosmiconfig": "^6.0.0",
-        "deepmerge": "^4.2.2",
-        "fs-extra": "^9.0.0",
-        "memfs": "^3.1.2",
-        "minimatch": "^3.0.4",
-        "schema-utils": "2.7.0",
-        "semver": "^7.3.2",
-        "tapable": "^1.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792302448&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
-          "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz",
-          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
-          "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
-          "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
-          "dev": true,
-          "optional": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1596294337050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz",
-          "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
-          "dev": true,
-          "optional": true
-        },
-        "lru-cache": {
-          "version": "6.0.0",
-          "resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-6.0.0.tgz",
-          "integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "yallist": "^4.0.0"
-          }
-        },
-        "schema-utils": {
-          "version": "2.7.0",
-          "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.7.0.tgz",
-          "integrity": "sha1-FxUfdtjq5n+793lgwzxnatn078c=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "@types/json-schema": "^7.0.4",
-            "ajv": "^6.12.2",
-            "ajv-keywords": "^3.4.1"
-          }
-        },
-        "semver": {
-          "version": "7.3.4",
-          "resolved": "https://registry.npm.taobao.org/semver/download/semver-7.3.4.tgz?cache=0&sync_timestamp=1606852122426&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.3.4.tgz",
-          "integrity": "sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "lru-cache": "^6.0.0"
-          }
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz",
-          "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        },
-        "yallist": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz",
-          "integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
-          "dev": true,
-          "optional": true
-        }
-      }
-    },
     "form-data": {
       "version": "2.3.3",
       "resolved": "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz",
@@ -15883,87 +15918,6 @@
         }
       }
     },
-    "vue-loader-v16": {
-      "version": "npm:vue-loader@16.1.2",
-      "resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.2.tgz?cache=0&sync_timestamp=1608187974157&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-16.1.2.tgz",
-      "integrity": "sha1-XAO2xQ0qX5g8fOuhXFDXjKKymPQ=",
-      "dev": true,
-      "optional": true,
-      "requires": {
-        "chalk": "^4.1.0",
-        "hash-sum": "^2.0.0",
-        "loader-utils": "^2.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792302448&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
-          "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz",
-          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
-          "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
-          "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
-          "dev": true,
-          "optional": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1596294337050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz",
-          "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
-          "dev": true,
-          "optional": true
-        },
-        "loader-utils": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
-          "integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^2.1.2"
-          }
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz",
-          "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
-      }
-    },
     "vue-router": {
       "version": "4.0.1",
       "resolved": "https://registry.npm.taobao.org/vue-router/download/vue-router-4.0.1.tgz?cache=0&sync_timestamp=1607347245114&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-4.0.1.tgz",

+ 5 - 4
src/views/Editor/Canvas/hooks/useAlignElementToCanvas.ts

@@ -1,23 +1,24 @@
 import { Ref, computed } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
-import { PPTElement } from '@/types/slides'
+import { PPTElement, Slide } from '@/types/slides'
 import { ElementAlignCommand, ElementAlignCommands } from '@/types/edit'
-import { getElementListRange } from '../utils/elementRange'
+import { getElementListRange } from '@/utils/element'
 import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
 
-export default (elementList: Ref<PPTElement[]>) => {
+export default () => {
   const store = useStore<State>()
 
   const activeElementIdList = computed(() => store.state.activeElementIdList)
   const activeElementList: Ref<PPTElement[]> = computed(() => store.getters.activeElementList)
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
 
   const alignElementToCanvas = (command: ElementAlignCommand) => {
     const viewportWidth = VIEWPORT_SIZE
     const viewportHeight = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
     const { minX, maxX, minY, maxY } = getElementListRange(activeElementList.value)
   
-    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList.value))
+    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
     for(const element of newElementList) {
       if(!activeElementIdList.value.includes(element.elId)) continue
       

+ 5 - 4
src/views/Editor/Canvas/hooks/useCombineElement.ts

@@ -1,19 +1,20 @@
 import { Ref, computed } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
-import { PPTElement } from '@/types/slides'
+import { PPTElement, Slide } from '@/types/slides'
 import { createRandomCode } from '@/utils/common'
 
-export default (elementList: Ref<PPTElement[]>) => {
+export default () => {
   const store = useStore<State>()
   const activeElementIdList = computed(() => store.state.activeElementIdList)
   const activeElementList: Ref<PPTElement[]> = computed(() => store.getters.activeElementList)
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
 
   // 组合元素(为当前所有激活元素添加一个相同的groupId)
   const combineElements = () => {
     if(!activeElementList.value.length) return
 
-    let newElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
+    let newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
     const groupId = createRandomCode()
 
     const combineElementList: PPTElement[] = []
@@ -41,7 +42,7 @@ export default (elementList: Ref<PPTElement[]>) => {
     const hasElementInGroup = activeElementList.value.some(item => item.groupId)
     if(!hasElementInGroup) return
     
-    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
+    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
     for(const element of newElementList) {
       if(activeElementIdList.value.includes(element.elId) && element.groupId) delete element.groupId
     }

+ 3 - 2
src/views/Editor/Canvas/hooks/useCopyAndPasteElement.ts

@@ -6,13 +6,15 @@ import { copyText, readClipboard } from '@/utils/clipboard'
 import { encrypt } from '@/utils/crypto'
 import { message } from 'ant-design-vue'
 import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
+import useDeleteElement from './useDeleteElement'
 
-export default (deleteElement: () => void) => {
+export default () => {
   const store = useStore<State>()
   const activeElementIdList = computed(() => store.state.activeElementIdList)
   const activeElementList: Ref<PPTElement[]> = computed(() => store.getters.activeElementList)
 
   const { pasteTextClipboardData } = usePasteTextClipboardData()
+  const { deleteElement } = useDeleteElement()
 
   const copyElement = () => {
     if(!activeElementIdList.value.length) return
@@ -24,7 +26,6 @@ export default (deleteElement: () => void) => {
 
     copyText(text).then(() => {
       store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
-      message.success('元素已复制到剪贴板', 0.8)
     })
   }
 

+ 5 - 4
src/views/Editor/Canvas/hooks/useDeleteElement.ts

@@ -1,21 +1,22 @@
 import { Ref, computed } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
-import { PPTElement } from '@/types/slides'
+import { Slide } from '@/types/slides'
 
-export default (elementList: Ref<PPTElement[]>) => {
+export default () => {
   const store = useStore<State>()
   const activeElementIdList = computed(() => store.state.activeElementIdList)
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
 
   const deleteElement = () => {
     if(!activeElementIdList.value.length) return
-    const newElementList = elementList.value.filter(el => !activeElementIdList.value.includes(el.elId))
+    const newElementList = currentSlide.value.elements.filter(el => !activeElementIdList.value.includes(el.elId))
     store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
     store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
   }
 
   const deleteAllElements = () => {
-    if(!elementList.value.length) return
+    if(!currentSlide.value.elements.length) return
     store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
     store.commit(MutationTypes.UPDATE_SLIDE, { elements: [] })
   }

+ 7 - 6
src/views/Editor/Canvas/hooks/useLockElement.ts

@@ -1,23 +1,24 @@
 import { useStore } from 'vuex'
 import { Ref, computed } from 'vue'
 import { State, MutationTypes } from '@/store'
-import { PPTElement } from '@/types/slides'
+import { PPTElement, Slide } from '@/types/slides'
 
-export default (elementList: Ref<PPTElement[]>) => {
+export default () => {
   const store = useStore<State>()
   const activeElementIdList = computed(() => store.state.activeElementIdList)
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
 
-  const lockElement = (handleElement: PPTElement) => {
-    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList.value))
+  const lockElement = () => {
+    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
   
     for(const element of newElementList) {
-      if(activeElementIdList.value.includes(handleElement.elId)) element.isLock = true
+      if(activeElementIdList.value.includes(element.elId)) element.isLock = true
     }
     store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
   }
 
   const unlockElement = (handleElement: PPTElement) => {
-    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList.value))
+    const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
 
     if(handleElement.groupId) {
       for(const element of newElementList) {

+ 4 - 3
src/views/Editor/Canvas/hooks/useMoveElement.ts

@@ -1,15 +1,16 @@
 import { Ref, computed } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
-import { PPTElement } from '@/types/slides'
+import { Slide } from '@/types/slides'
 import { KEYS } from '@/configs/hotkey'
 
-export default (elementList: Ref<PPTElement[]>) => {
+export default () => {
   const store = useStore<State>()
   const activeElementIdList = computed(() => store.state.activeElementIdList)
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
 
   const moveElement = (command: string) => {
-    const newElementList = elementList.value.map(el => {
+    const newElementList = currentSlide.value.elements.map(el => {
       if(activeElementIdList.value.includes(el.elId)) {
         let { left, top } = el
         switch(command) {

+ 8 - 7
src/views/Editor/Canvas/hooks/useOrderElement.ts

@@ -1,11 +1,12 @@
+import { Ref, computed } from 'vue'
 import { useStore } from 'vuex'
-import { Ref } from 'vue'
 import { State, MutationTypes } from '@/store'
-import { PPTElement } from '@/types/slides'
+import { PPTElement, Slide } from '@/types/slides'
 import { ElementOrderCommand, ElementOrderCommands } from '@/types/edit'
 
-export default (elementList: Ref<PPTElement[]>) => {
+export default () => {
   const store = useStore<State>()
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
 
   // 获取组合元素层级范围(组合成员中的最大层级和最小层级)
   const getCombineElementIndexRange = (elementList: PPTElement[], combineElementList: PPTElement[]) => {
@@ -167,10 +168,10 @@ export default (elementList: Ref<PPTElement[]>) => {
   const orderElement = (element: PPTElement, command: ElementOrderCommand) => {
     let newElementList = null
     
-    if(command === ElementOrderCommands.UP) newElementList = moveUpElement(elementList.value, element)
-    else if(command === ElementOrderCommands.DOWN) newElementList = moveDownElement(elementList.value, element)
-    else if(command === ElementOrderCommands.TOP) newElementList = moveTopElement(elementList.value, element)
-    else if(command === ElementOrderCommands.BOTTOM) newElementList = moveBottomElement(elementList.value, element)
+    if(command === ElementOrderCommands.UP) newElementList = moveUpElement(currentSlide.value.elements, element)
+    else if(command === ElementOrderCommands.DOWN) newElementList = moveDownElement(currentSlide.value.elements, element)
+    else if(command === ElementOrderCommands.TOP) newElementList = moveTopElement(currentSlide.value.elements, element)
+    else if(command === ElementOrderCommands.BOTTOM) newElementList = moveBottomElement(currentSlide.value.elements, element)
 
     store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
   }

+ 14 - 6
src/hooks/usePasteTextClipboardData.ts

@@ -5,6 +5,11 @@ import { decrypt } from '@/utils/crypto'
 import { PPTElement, Slide } from '@/types/slides'
 import { createRandomCode } from '@/utils/common'
 
+interface PasteTextClipboardDataOptions {
+  onlySlide?: boolean;
+  onlyElements?: boolean;
+}
+
 export default () => {
   const store = useStore<State>()
   const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
@@ -37,15 +42,18 @@ export default () => {
     store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, Object.values(elIdMap))
   }
 
-  const pasteSlide = (slides: Slide[]) => {
-    console.log(slides)
+  const pasteSlide = (slide: Slide) => {
+    store.commit(MutationTypes.ADD_SLIDE, slide)
   }
 
   const pasteText = (text: string) => {
     console.log(text)
   }
 
-  const pasteTextClipboardData = (text: string) => {
+  const pasteTextClipboardData = (text: string, options?: PasteTextClipboardDataOptions) => {
+    const onlySlide = options?.onlySlide || false
+    const onlyElements = options?.onlyElements || false
+
     let clipboardData
     try {
       clipboardData = JSON.parse(decrypt(text))
@@ -58,12 +66,12 @@ export default () => {
     if(typeof clipboardData === 'object') {
       const { type, data } = clipboardData
 
-      if(type === 'elements') pasteElement(data)
-      else if(type === 'slide') pasteSlide(data)
+      if(type === 'elements' && !onlySlide) pasteElement(data)
+      else if(type === 'slide' && !onlyElements) pasteSlide(data)
     }
 
     // 粘贴普通文本
-    else pasteText(clipboardData)
+    else if(!onlyElements && !onlySlide) pasteText(clipboardData)
   }
 
   return {

+ 19 - 0
src/hooks/useSelectAllElement.ts

@@ -0,0 +1,19 @@
+import { Ref, computed } from 'vue'
+import { useStore } from 'vuex'
+import { State, MutationTypes } from '@/store'
+import { Slide } from '@/types/slides'
+
+export default () => {
+  const store = useStore<State>()
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
+
+  const selectAllElement = () => {
+    const unlockedElements = currentSlide.value.elements.filter(el => !el.isLock)
+    const newActiveElementIdList = unlockedElements.map(el => el.elId)
+    store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, newActiveElementIdList)
+  }
+
+  return {
+    selectAllElement,
+  }
+}

+ 78 - 0
src/hooks/useSlideHandler.ts

@@ -0,0 +1,78 @@
+import { Ref, computed } from 'vue'
+import { useStore } from 'vuex'
+import { State, MutationTypes } from '@/store'
+import { Slide } from '@/types/slides'
+import { createRandomCode } from '@/utils/common'
+import { copyText, readClipboard } from '@/utils/clipboard'
+import { encrypt } from '@/utils/crypto'
+import { KEYS } from '@/configs/hotkey'
+import { message } from 'ant-design-vue'
+import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
+
+export default () => {
+  const store = useStore<State>()
+  const slideIndex = computed(() => store.state.slideIndex)
+  const slidesLength = computed(() => store.state.slides.length)
+  const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
+
+  const { pasteTextClipboardData } = usePasteTextClipboardData()
+
+  const updateSlideIndex = (command: string) => {
+    let targetIndex = 0
+    if(command === KEYS.UP && slideIndex.value > 0) {
+      targetIndex = slideIndex.value - 1
+    }
+    else if(command === KEYS.DOWN && slideIndex.value < slidesLength.value - 1) {
+      targetIndex = slideIndex.value + 1
+    }
+    store.commit(MutationTypes.UPDATE_SLIDE_INDEX, targetIndex)
+  }
+
+  const copySlide = () => {
+    const text = encrypt(JSON.stringify({
+      type: 'slide',
+      data: currentSlide.value,
+    }))
+
+    copyText(text).then(() => {
+      store.commit(MutationTypes.SET_THUMBNAILS_FOCUS, true)
+    })
+  }
+
+  const pasteSlide = () => {
+    readClipboard().then(text => {
+      pasteTextClipboardData(text, { onlySlide: true })
+    }).catch(err => message.warning(err))
+  }
+
+  const createSlide = () => {
+    const emptySlide = {
+      id: createRandomCode(8),
+      elements: [],
+    }
+    store.commit(MutationTypes.ADD_SLIDE, emptySlide)
+  }
+
+  const copyAndPasteSlide = () => {
+    store.commit(MutationTypes.ADD_SLIDE, currentSlide.value)
+  }
+
+  const deleteSlide = () => {
+    store.commit(MutationTypes.DELETE_SLIDE, currentSlide.value.id)
+  }
+
+  const cutSlide = () => {
+    copySlide()
+    deleteSlide()
+  }
+
+  return {
+    updateSlideIndex,
+    copySlide,
+    pasteSlide,
+    createSlide,
+    copyAndPasteSlide,
+    deleteSlide,
+    cutSlide,
+  }
+}

+ 1 - 1
src/store/index.ts

@@ -29,7 +29,7 @@ export interface State {
 const state: State = {
   activeElementIdList: [],
   handleElementId: '',
-  editorAreaShowScale: 85,
+  editorAreaShowScale: 90,
   thumbnailsFocus: false,
   editorAreaFocus: false,
   disableHotkeys: false,

+ 3 - 5
src/store/mutations.ts

@@ -56,11 +56,9 @@ export const mutations: MutationTree<State> = {
     state.slides = slides
   },
 
-  [MutationTypes.ADD_SLIDE](state, data: AddSlideData) {
-    const { index, slide } = data
-    const slides = Array.isArray(slide) ? slide : [slide]
-    const addIndex = index !== undefined ? index : (state.slideIndex + 1)
-    state.slides.splice(addIndex, 0, ...slides)
+  [MutationTypes.ADD_SLIDE](state, slide: Slide) {
+    const addIndex = state.slideIndex + 1
+    state.slides.splice(addIndex, 0, slide)
     state.slideIndex = addIndex
   },
 

+ 18 - 0
src/types/edit.ts

@@ -52,4 +52,22 @@ export enum OPERATE_KEYS {
   LEFT_BOTTOM = 6,
   BOTTOM = 7,
   RIGHT_BOTTOM = 8,
+}
+
+export interface AlignmentLineAxis {
+  x: number; 
+  y: number;
+}
+
+export interface AlignmentLineProps {
+  type: 'vertical' | 'horizontal';
+  axis: AlignmentLineAxis;
+  length: number;
+}
+
+export interface MultiSelectRange {
+  minX: number;
+  maxX: number;
+  minY: number;
+  maxY: number;
 }

+ 115 - 92
src/views/Editor/Canvas/utils/elementRange.ts

@@ -1,93 +1,116 @@
-import { PPTElement } from '@/types/slides'
-
-// 获取矩形旋转后在画布中的位置范围
-interface RotatedElementData {
-  left: number;
-  top: number;
-  width: number;
-  height: number;
-  rotate: number;
-}
-export const getRectRotatedRange = (element: RotatedElementData) => {
-  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 { left, top, width, height, rotate } = element
-    const { xRange, yRange } = getRectRotatedRange({ left, top, width, height, rotate })
-    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 }
+import { PPTElement } from '@/types/slides'
+
+// 获取矩形旋转后在画布中的位置范围
+interface RotatedElementData {
+  left: number;
+  top: number;
+  width: number;
+  height: number;
+  rotate: number;
+}
+export const getRectRotatedRange = (element: RotatedElementData) => {
+  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 { left, top, width, height, rotate } = element
+    const { xRange, yRange } = getRectRotatedRange({ left, top, width, height, rotate })
+    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 }
+}
+
+export 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
 }

+ 4 - 0
src/utils/emitter.ts

@@ -1,5 +1,9 @@
 import mitt, { Emitter } from 'mitt'
 
+export enum EMITTER_EVENTS {
+  
+}
+
 const emitter: Emitter = mitt()
 
 export default emitter

+ 1 - 1
src/views/Editor/Canvas/AlignmentLine.vue

@@ -15,7 +15,7 @@
 
 <script lang="ts">
 import { PropType } from 'vue'
-import { AlignmentLineAxis } from './types/index'
+import { AlignmentLineAxis } from '@/types/edit'
 
 export default {
   name: 'alignment-line',

+ 2 - 4
src/views/Editor/Canvas/MultiSelectOperate.vue

@@ -26,14 +26,12 @@ import { computed, defineComponent, reactive, PropType, watchEffect, toRefs } fr
 import { useStore } from 'vuex'
 import { State } from '@/store'
 import { PPTElement, ElementTypes } from '@/types/slides'
-import { getElementListRange } from './utils/elementRange'
-import { ElementScaleHandler, OperateResizablePointTypes, OperateBorderLineTypes, OPERATE_KEYS } from '@/types/edit'
+import { getElementListRange } from '@/utils/element'
+import { ElementScaleHandler, OperateResizablePointTypes, OperateBorderLineTypes, MultiSelectRange, OPERATE_KEYS } from '@/types/edit'
 
 import ResizablePoint from '@/views/_common/_operate/ResizablePoint.vue'
 import BorderLine from '@/views/_common/_operate/BorderLine.vue'
 
-import { MultiSelectRange } from './types/index'
-
 export default defineComponent({
   name: 'multi-select-operate',
   components: {

+ 2 - 3
src/views/Editor/Canvas/hooks/useDragElement.ts

@@ -2,10 +2,9 @@ import { Ref, computed } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { ElementTypes, PPTElement } from '@/types/slides'
-import { AlignmentLineProps } from '../types/index'
+import { AlignmentLineProps } from '@/types/edit'
 import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
-import { getRectRotatedRange } from '../utils/elementRange'
-import { AlignLine, uniqAlignLines } from '../utils/alignLines'
+import { getRectRotatedRange, AlignLine, uniqAlignLines } from '@/utils/element'
 
 export default (
   elementList: Ref<PPTElement[]>,

+ 1 - 1
src/views/Editor/Canvas/hooks/useMouseSelection.ts

@@ -2,7 +2,7 @@ import { Ref, reactive } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { PPTElement } from '@/types/slides'
-import { getElementRange } from '../utils/elementRange'
+import { getElementRange } from '@/utils/element'
 
 export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | null>, canvasScale: Ref<number>) => {
   const store = useStore<State>()

+ 2 - 3
src/views/Editor/Canvas/hooks/useScaleElement.ts

@@ -2,10 +2,9 @@ import { computed, Ref } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { ElementTypes, PPTElement, PPTImageElement, PPTLineElement, PPTShapeElement } from '@/types/slides'
-import { OPERATE_KEYS, ElementScaleHandler } from '@/types/edit'
+import { OPERATE_KEYS, ElementScaleHandler, AlignmentLineProps, MultiSelectRange } from '@/types/edit'
 import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
-import { AlignLine, uniqAlignLines } from '../utils/alignLines'
-import { AlignmentLineProps, MultiSelectRange } from '../types/index'
+import { AlignLine, uniqAlignLines } from '@/utils/element'
 
 // 计算元素被旋转一定角度后,八个操作点的新坐标
 interface RotateElementData {

+ 13 - 35
src/views/Editor/Canvas/index.vue

@@ -56,15 +56,6 @@
         :selectElement="selectElement"
         :rotateElement="rotateElement"
         :scaleElement="scaleElement"
-        :orderElement="orderElement"
-        :combineElements="combineElements"
-        :uncombineElements="uncombineElements"
-        :alignElementToCanvas="alignElementToCanvas"
-        :deleteElement="deleteElement"
-        :lockElement="lockElement"
-        :unlockElement="unlockElement"
-        :copyElement="copyElement"
-        :cutElement="cutElement"
       />
     </div>
   </div>
@@ -76,21 +67,19 @@ import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 import { PPTElement, Slide } from '@/types/slides'
-import { AlignmentLineProps } from './types/index'
+import { AlignmentLineProps } from '@/types/edit'
 
 import useViewportSize from './hooks/useViewportSize'
-import useLockElement from './hooks/useLockElement'
-import useDeleteElement from './hooks/useDeleteElement'
-import useCombineElement from './hooks/useCombineElement'
-import useOrderElement from './hooks/useOrderElement'
-import useAlignElementToCanvas from './hooks/useAlignElementToCanvas'
-import useCopyAndPasteElement from './hooks/useCopyAndPasteElement'
+import useMouseSelection from './hooks/useMouseSelection'
+import useDropImageElement from './hooks/useDropImageElement'
 import useRotateElement from './hooks/useRotateElement'
 import useScaleElement from './hooks/useScaleElement'
 import useSelectElement from './hooks/useSelectElement'
 import useDragElement from './hooks/useDragElement'
-import useMouseSelection from './hooks/useMouseSelection'
-import useDropImageElement from './hooks/useDropImageElement'
+
+import useDeleteElement from '@/hooks/useDeleteElement'
+import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
+import useSelectAllElement from '@/hooks/useSelectAllElement'
 
 import EditableElement from '@/views/_common/_element/EditableElement.vue'
 import MouseSelection from './MouseSelection.vue'
@@ -137,15 +126,13 @@ export default defineComponent({
     const { mouseSelectionState, updateMouseSelection } = useMouseSelection(elementList, viewportRef, canvasScale)
 
     const { dragElement } = useDragElement(elementList, activeGroupElementId, canvasScale, alignmentLines)
-    const { selectElement, selectAllElement } = useSelectElement(elementList, activeGroupElementId, dragElement)
+    const { selectElement } = useSelectElement(elementList, activeGroupElementId, dragElement)
     const { scaleElement, scaleMultiElement } = useScaleElement(elementList, canvasScale, activeGroupElementId, alignmentLines)
-    const { rotateElement } = useRotateElement(elementList, viewportRef, canvasScale)  
-    const { orderElement } = useOrderElement(elementList)
-    const { alignElementToCanvas } = useAlignElementToCanvas(elementList)
-    const { combineElements, uncombineElements } = useCombineElement(elementList)
-    const { deleteElement, deleteAllElements } = useDeleteElement(elementList)
-    const { lockElement, unlockElement } = useLockElement(elementList)
-    const { copyElement, cutElement, pasteElement } = useCopyAndPasteElement(deleteElement)
+    const { rotateElement } = useRotateElement(elementList, viewportRef, canvasScale)
+
+    const { selectAllElement } = useSelectAllElement()
+    const { deleteAllElements } = useDeleteElement()
+    const { pasteElement } = useCopyAndPasteElement()
 
     const handleClickBlankArea = (e: MouseEvent) => {
       store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
@@ -195,15 +182,6 @@ export default defineComponent({
       rotateElement,
       scaleElement,
       scaleMultiElement,
-      orderElement,
-      combineElements,
-      uncombineElements,
-      alignElementToCanvas,
-      deleteElement,
-      lockElement,
-      unlockElement,
-      copyElement,
-      cutElement,
       contextmenus,
     }
   },

+ 0 - 17
src/views/Editor/Canvas/types/index.ts

@@ -1,17 +0,0 @@
-export interface AlignmentLineAxis {
-  x: number; 
-  y: number;
-}
-
-export interface AlignmentLineProps {
-  type: 'vertical' | 'horizontal';
-  axis: AlignmentLineAxis;
-  length: number;
-}
-
-export interface MultiSelectRange {
-  minX: number;
-  maxX: number;
-  minY: number;
-  maxY: number;
-}

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

@@ -1,22 +0,0 @@
-export 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
-}

+ 11 - 20
src/views/Editor/Thumbnails/index.vue

@@ -23,7 +23,7 @@
           @mousedown="changSlideIndex(index)"
           v-contextmenu="contextmenus"
         >
-          <div class="slide-index">{{ fillDigit(index + 1) }}</div>
+          <div class="slide-index">{{ fillDigit(index + 1, 2) }}</div>
           <div class="thumbnail"></div>
         </div>
       </template>
@@ -38,6 +38,7 @@ import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { fillDigit } from '@/utils/common'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
+import useSlideHandler from '@/hooks/useSlideHandler'
 
 export default defineComponent({
   name: 'thumbnails',
@@ -49,6 +50,15 @@ export default defineComponent({
     const slides = computed(() => store.state.slides)
     const slideIndex = computed(() => store.state.slideIndex)
 
+    const {
+      copySlide,
+      pasteSlide,
+      createSlide,
+      copyAndPasteSlide,
+      deleteSlide,
+      cutSlide,
+    } = useSlideHandler()
+
     const changSlideIndex = (index: number) => {
       store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
 
@@ -75,25 +85,6 @@ export default defineComponent({
       store.commit(MutationTypes.UPDATE_SLIDE_INDEX, newIndex)
     }
 
-    const cutSlide = () => {
-      console.log('cutSlide')
-    }
-    const copySlide = () => {
-      console.log('copySlide')
-    }
-    const pasteSlide = () => {
-      console.log('pasteSlide')
-    }
-    const createSlide = () => {
-      console.log('createSlide')
-    }
-    const copyAndPasteSlide = () => {
-      console.log('copyAndPasteSlide')
-    }
-    const deleteSlide = () => {
-      console.log('deleteSlide')
-    }
-
     const contextmenus = (): ContextmenuItem[] => {
       return [
         {

+ 58 - 10
src/views/Editor/useHotkey.ts

@@ -2,50 +2,98 @@ import { computed, onMounted, onUnmounted } from 'vue'
 import { useStore } from 'vuex'
 import { State, MutationTypes } from '@/store'
 import { KEYS } from '@/configs/hotkey'
-
 import { message } from 'ant-design-vue'
 
+import useSlideHandler from '@/hooks/useSlideHandler'
+import useLockElement from '@/hooks/useLockElement'
+import useDeleteElement from '@/hooks/useDeleteElement'
+import useCombineElement from '@/hooks/useCombineElement'
+import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
+import useSelectAllElement from '@/hooks/useSelectAllElement'
+import useMoveElement from '@/hooks/useMoveElement'
+
 export default () => {
   const store = useStore<State>()
 
   const ctrlKeyActive = computed(() => store.state.ctrlKeyState)
   const shiftKeyActive = computed(() => store.state.shiftKeyState)
+  const disableHotkeys = computed(() => store.state.disableHotkeys)
+  const activeElementIdList = computed(() => store.state.activeElementIdList)
 
   const editorAreaFocus = computed(() => store.state.editorAreaFocus)
   const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
 
+  const {
+    updateSlideIndex,
+    copySlide,
+    createSlide,
+    deleteSlide,
+    cutSlide,
+  } = useSlideHandler()
+
+  const { combineElements, uncombineElements } = useCombineElement()
+  const { deleteElement } = useDeleteElement()
+  const { lockElement } = useLockElement()
+  const { copyElement, cutElement } = useCopyAndPasteElement()
+  const { selectAllElement } = useSelectAllElement()
+  const { moveElement } = useMoveElement()
+
   const copy = () => {
-    message.success('copy')
+    if(disableHotkeys.value) return
+    if(thumbnailsFocus.value) copySlide()
+    else if(activeElementIdList.value.length) copyElement()
   }
+
   const cut = () => {
-    message.success('cut')
+    if(disableHotkeys.value) return
+    if(thumbnailsFocus.value) cutSlide()
+    else if(activeElementIdList.value.length) cutElement()
   }
+
   const undo = () => {
     message.success('undo')
   }
+
   const redo = () => {
     message.success('redo')
   }
+
   const selectAll = () => {
-    message.success('selectAll')
+    if(!editorAreaFocus.value && disableHotkeys.value) return
+    selectAllElement()
   }
+
   const lock = () => {
-    message.success('lock')
+    if(!editorAreaFocus.value && disableHotkeys.value) return
+    lockElement()
   }
   const combine = () => {
-    message.success('combine')
+    if(!editorAreaFocus.value && disableHotkeys.value) return
+    combineElements()
   }
+
   const uncombine = () => {
-    message.success('uncombine')
+    if(!editorAreaFocus.value && disableHotkeys.value) return
+    uncombineElements()
   }
+
   const remove = () => {
-    message.success('remove')
+    if(disableHotkeys.value) return
+    if(thumbnailsFocus.value) deleteSlide()
+    else if(activeElementIdList.value.length) deleteElement()
   }
+
   const move = (key: string) => {
-    message.success(`move: ${key}`)
+    if(disableHotkeys.value) return
+    if(thumbnailsFocus.value && (key === KEYS.UP || key === KEYS.DOWN)) {
+      updateSlideIndex(key)
+    }
+    else if(activeElementIdList.value.length) moveElement(key)
   }
+
   const create = () => {
-    message.success('create')
+    if(!thumbnailsFocus.value || disableHotkeys.value) return
+    createSlide()
   }
 
   const keydownListener = (e: KeyboardEvent) => {

+ 31 - 59
src/views/_common/_element/EditableElement.vue

@@ -27,13 +27,14 @@ import { computed, defineComponent, PropType } from 'vue'
 import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement, PPTLineElement } from '@/types/slides'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 
-import {
-  ElementOrderCommand,
-  ElementOrderCommands,
-  ElementAlignCommand,
-  ElementAlignCommands,
-  ElementScaleHandler,
-} from '@/types/edit'
+import useLockElement from '@/hooks/useLockElement'
+import useDeleteElement from '@/hooks/useDeleteElement'
+import useCombineElement from '@/hooks/useCombineElement'
+import useOrderElement from '@/hooks/useOrderElement'
+import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
+import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
+
+import { ElementOrderCommands, ElementAlignCommands, ElementScaleHandler } from '@/types/edit'
 
 import ImageElement from './ImageElement/index.vue'
 import TextElement from './TextElement/index.vue'
@@ -85,42 +86,6 @@ export default defineComponent({
       type: Function as PropType<(e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: ElementScaleHandler) => void>,
       required: true,
     },
-    orderElement: {
-      type: Function as PropType<(element: PPTElement, command: ElementOrderCommand) => void>,
-      required: true,
-    },
-    combineElements: {
-      type: Function as PropType<() => void>,
-      required: true,
-    },
-    uncombineElements: {
-      type: Function as PropType<() => void>,
-      required: true,
-    },
-    alignElementToCanvas: {
-      type: Function as PropType<(command: ElementAlignCommand) => void>,
-      required: true,
-    },
-    deleteElement: {
-      type: Function as PropType<() => void>,
-      required: true,
-    },
-    lockElement: {
-      type: Function as PropType<(element: PPTElement) => void>,
-      required: true,
-    },
-    unlockElement: {
-      type: Function as PropType<(element: PPTElement) => void>,
-      required: true,
-    },
-    copyElement: {
-      type: Function as PropType<() => void>,
-      required: true,
-    },
-    cutElement: {
-      type: Function as PropType<() => void>,
-      required: true,
-    },
   },
   setup(props) {
     const currentElementComponent = computed(() => {
@@ -131,12 +96,19 @@ export default defineComponent({
       return elementTypeMap[props.elementInfo.type] || null
     })
 
+    const { orderElement } = useOrderElement()
+    const { alignElementToCanvas } = useAlignElementToCanvas()
+    const { combineElements, uncombineElements } = useCombineElement()
+    const { deleteElement } = useDeleteElement()
+    const { lockElement, unlockElement } = useLockElement()
+    const { copyElement, cutElement } = useCopyAndPasteElement()
+
     const contextmenus = (): ContextmenuItem[] => {
       if(props.elementInfo.isLock) {
         return [{
           text: '解锁', 
           icon: 'icon-unlock',
-          handler: () => props.unlockElement(props.elementInfo),
+          handler: () => unlockElement(props.elementInfo),
         }]
       }
 
@@ -145,13 +117,13 @@ export default defineComponent({
           text: '剪切',
           subText: 'Ctrl + X',
           icon: 'icon-scissor',
-          handler: props.cutElement,
+          handler: cutElement,
         },
         {
           text: '复制',
           subText: 'Ctrl + C',
           icon: 'icon-copy',
-          handler: props.copyElement,
+          handler: copyElement,
         },
         { divider: true },
         {
@@ -159,29 +131,29 @@ export default defineComponent({
           icon: 'icon-top-layer',
           disable: props.isMultiSelect && !props.elementInfo.groupId,
           children: [
-            { text: '置顶层', handler: () => props.orderElement(props.elementInfo, ElementOrderCommands.TOP) },
-            { text: '置底层', handler: () => props.orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },
+            { text: '置顶层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.TOP) },
+            { text: '置底层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },
             { divider: true },
-            { text: '上移一层', handler: () => props.orderElement(props.elementInfo, ElementOrderCommands.UP) },
-            { text: '下移一层', handler: () => props.orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
+            { text: '上移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.UP) },
+            { text: '下移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
           ],
         },
         {
           text: '水平对齐',
           icon: 'icon-align-left',
           children: [
-            { text: '水平居中', handler: () => props.alignElementToCanvas(ElementAlignCommands.HORIZONTAL) },
-            { text: '左对齐', handler: () => props.alignElementToCanvas(ElementAlignCommands.LEFT) },
-            { text: '右对齐', handler: () => props.alignElementToCanvas(ElementAlignCommands.RIGHT) },
+            { text: '水平居中', handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL) },
+            { text: '左对齐', handler: () => alignElementToCanvas(ElementAlignCommands.LEFT) },
+            { text: '右对齐', handler: () => alignElementToCanvas(ElementAlignCommands.RIGHT) },
           ],
         },
         {
           text: '垂直对齐',
           icon: 'icon-align-bottom',
           children: [
-            { text: '垂直居中', handler: () => props.alignElementToCanvas(ElementAlignCommands.VERTICAL) },
-            { text: '上对齐', handler: () => props.alignElementToCanvas(ElementAlignCommands.TOP) },
-            { text: '下对齐', handler: () => props.alignElementToCanvas(ElementAlignCommands.BOTTOM) },
+            { text: '垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL) },
+            { text: '上对齐', handler: () => alignElementToCanvas(ElementAlignCommands.TOP) },
+            { text: '下对齐', handler: () => alignElementToCanvas(ElementAlignCommands.BOTTOM) },
           ],
         },
         { divider: true },
@@ -189,20 +161,20 @@ export default defineComponent({
           text: props.elementInfo.groupId ? '取消组合' : '组合',
           subText: 'Ctrl + G',
           icon: 'icon-block',
-          handler: props.elementInfo.groupId ? props.uncombineElements : props.combineElements,
+          handler: props.elementInfo.groupId ? uncombineElements : combineElements,
           hide: !props.isMultiSelect,
         },
         {
           text: '锁定',
           subText: 'Ctrl + L',
           icon: 'icon-lock',
-          handler: () => props.lockElement(props.elementInfo),
+          handler: lockElement,
         },
         {
           text: '删除',
           subText: 'Delete',
           icon: 'icon-delete',
-          handler: props.deleteElement,
+          handler: deleteElement,
         },
       ]
     }