From 5f20da11b339f7371e5d1853d87b0768b910a40c Mon Sep 17 00:00:00 2001 From: pipipi-pikachu <1171051090@qq.com> Date: Tue, 22 Dec 2020 17:28:14 +0800 Subject: [PATCH] update --- package-lock.json | 348 ++++++++---------- .../hooks/useAlignElementToCanvas.ts | 9 +- .../Canvas => }/hooks/useCombineElement.ts | 9 +- .../hooks/useCopyAndPasteElement.ts | 5 +- .../Canvas => }/hooks/useDeleteElement.ts | 9 +- .../Canvas => }/hooks/useLockElement.ts | 13 +- .../Canvas => }/hooks/useMoveElement.ts | 7 +- .../Canvas => }/hooks/useOrderElement.ts | 15 +- src/hooks/usePasteTextClipboardData.ts | 20 +- src/hooks/useSelectAllElement.ts | 19 + src/hooks/useSlideHandler.ts | 78 ++++ src/store/index.ts | 2 +- src/store/mutations.ts | 8 +- src/types/edit.ts | 18 + .../elementRange.ts => utils/element.ts} | 207 ++++++----- src/utils/emitter.ts | 4 + src/views/Editor/Canvas/AlignmentLine.vue | 2 +- .../Editor/Canvas/MultiSelectOperate.vue | 6 +- .../Editor/Canvas/hooks/useDragElement.ts | 5 +- .../Editor/Canvas/hooks/useMouseSelection.ts | 2 +- .../Editor/Canvas/hooks/useScaleElement.ts | 5 +- src/views/Editor/Canvas/index.vue | 48 +-- src/views/Editor/Canvas/types/index.ts | 17 - src/views/Editor/Canvas/utils/alignLines.ts | 22 -- src/views/Editor/Thumbnails/index.vue | 31 +- src/views/Editor/useHotkey.ts | 68 +++- .../_common/_element/EditableElement.vue | 90 ++--- 27 files changed, 561 insertions(+), 506 deletions(-) rename src/{views/Editor/Canvas => }/hooks/useAlignElementToCanvas.ts (89%) rename src/{views/Editor/Canvas => }/hooks/useCombineElement.ts (91%) rename src/{views/Editor/Canvas => }/hooks/useCopyAndPasteElement.ts (91%) rename src/{views/Editor/Canvas => }/hooks/useDeleteElement.ts (68%) rename src/{views/Editor/Canvas => }/hooks/useLockElement.ts (75%) rename src/{views/Editor/Canvas => }/hooks/useMoveElement.ts (78%) rename src/{views/Editor/Canvas => }/hooks/useOrderElement.ts (95%) create mode 100644 src/hooks/useSelectAllElement.ts create mode 100644 src/hooks/useSlideHandler.ts rename src/{views/Editor/Canvas/utils/elementRange.ts => utils/element.ts} (74%) delete mode 100644 src/views/Editor/Canvas/types/index.ts delete mode 100644 src/views/Editor/Canvas/utils/alignLines.ts diff --git a/package-lock.json b/package-lock.json index 00957ee1..f3c3b8f8 100644 --- a/package-lock.json +++ b/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", diff --git a/src/views/Editor/Canvas/hooks/useAlignElementToCanvas.ts b/src/hooks/useAlignElementToCanvas.ts similarity index 89% rename from src/views/Editor/Canvas/hooks/useAlignElementToCanvas.ts rename to src/hooks/useAlignElementToCanvas.ts index c52e0927..196e2c7d 100644 --- a/src/views/Editor/Canvas/hooks/useAlignElementToCanvas.ts +++ b/src/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) => { +export default () => { const store = useStore() const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementList: Ref = computed(() => store.getters.activeElementList) + const currentSlide: Ref = 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 diff --git a/src/views/Editor/Canvas/hooks/useCombineElement.ts b/src/hooks/useCombineElement.ts similarity index 91% rename from src/views/Editor/Canvas/hooks/useCombineElement.ts rename to src/hooks/useCombineElement.ts index c5a2d745..fb7bc85f 100644 --- a/src/views/Editor/Canvas/hooks/useCombineElement.ts +++ b/src/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) => { +export default () => { const store = useStore() const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementList: Ref = computed(() => store.getters.activeElementList) + const currentSlide: Ref = 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) => { 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 } diff --git a/src/views/Editor/Canvas/hooks/useCopyAndPasteElement.ts b/src/hooks/useCopyAndPasteElement.ts similarity index 91% rename from src/views/Editor/Canvas/hooks/useCopyAndPasteElement.ts rename to src/hooks/useCopyAndPasteElement.ts index 4d43f11d..4766cdc2 100644 --- a/src/views/Editor/Canvas/hooks/useCopyAndPasteElement.ts +++ b/src/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() const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementList: Ref = 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) }) } diff --git a/src/views/Editor/Canvas/hooks/useDeleteElement.ts b/src/hooks/useDeleteElement.ts similarity index 68% rename from src/views/Editor/Canvas/hooks/useDeleteElement.ts rename to src/hooks/useDeleteElement.ts index 32f5be1c..48b2d923 100644 --- a/src/views/Editor/Canvas/hooks/useDeleteElement.ts +++ b/src/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) => { +export default () => { const store = useStore() const activeElementIdList = computed(() => store.state.activeElementIdList) + const currentSlide: Ref = 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: [] }) } diff --git a/src/views/Editor/Canvas/hooks/useLockElement.ts b/src/hooks/useLockElement.ts similarity index 75% rename from src/views/Editor/Canvas/hooks/useLockElement.ts rename to src/hooks/useLockElement.ts index 16ee5ddb..970ef5ea 100644 --- a/src/views/Editor/Canvas/hooks/useLockElement.ts +++ b/src/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) => { +export default () => { const store = useStore() const activeElementIdList = computed(() => store.state.activeElementIdList) + const currentSlide: Ref = 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) { diff --git a/src/views/Editor/Canvas/hooks/useMoveElement.ts b/src/hooks/useMoveElement.ts similarity index 78% rename from src/views/Editor/Canvas/hooks/useMoveElement.ts rename to src/hooks/useMoveElement.ts index 95ed00bf..c424fa72 100644 --- a/src/views/Editor/Canvas/hooks/useMoveElement.ts +++ b/src/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) => { +export default () => { const store = useStore() const activeElementIdList = computed(() => store.state.activeElementIdList) + const currentSlide: Ref = 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) { diff --git a/src/views/Editor/Canvas/hooks/useOrderElement.ts b/src/hooks/useOrderElement.ts similarity index 95% rename from src/views/Editor/Canvas/hooks/useOrderElement.ts rename to src/hooks/useOrderElement.ts index ea722f2e..88bc4884 100644 --- a/src/views/Editor/Canvas/hooks/useOrderElement.ts +++ b/src/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) => { +export default () => { const store = useStore() + const currentSlide: Ref = computed(() => store.getters.currentSlide) // 获取组合元素层级范围(组合成员中的最大层级和最小层级) const getCombineElementIndexRange = (elementList: PPTElement[], combineElementList: PPTElement[]) => { @@ -167,10 +168,10 @@ export default (elementList: Ref) => { 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 }) } diff --git a/src/hooks/usePasteTextClipboardData.ts b/src/hooks/usePasteTextClipboardData.ts index a08d5953..653741d8 100644 --- a/src/hooks/usePasteTextClipboardData.ts +++ b/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() const currentSlide: Ref = 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 { diff --git a/src/hooks/useSelectAllElement.ts b/src/hooks/useSelectAllElement.ts new file mode 100644 index 00000000..29d54b22 --- /dev/null +++ b/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() + const currentSlide: Ref = 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, + } +} \ No newline at end of file diff --git a/src/hooks/useSlideHandler.ts b/src/hooks/useSlideHandler.ts new file mode 100644 index 00000000..9b2c0337 --- /dev/null +++ b/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() + const slideIndex = computed(() => store.state.slideIndex) + const slidesLength = computed(() => store.state.slides.length) + const currentSlide: Ref = 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, + } +} \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts index 4ceab26e..1dddc1dc 100644 --- a/src/store/index.ts +++ b/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, diff --git a/src/store/mutations.ts b/src/store/mutations.ts index f18cdd8b..35cafd03 100644 --- a/src/store/mutations.ts +++ b/src/store/mutations.ts @@ -56,11 +56,9 @@ export const mutations: MutationTree = { 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 }, diff --git a/src/types/edit.ts b/src/types/edit.ts index d072108f..14cb51ff 100644 --- a/src/types/edit.ts +++ b/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; } \ No newline at end of file diff --git a/src/views/Editor/Canvas/utils/elementRange.ts b/src/utils/element.ts similarity index 74% rename from src/views/Editor/Canvas/utils/elementRange.ts rename to src/utils/element.ts index 68baefdb..63f24d40 100644 --- a/src/views/Editor/Canvas/utils/elementRange.ts +++ b/src/utils/element.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 } \ No newline at end of file diff --git a/src/utils/emitter.ts b/src/utils/emitter.ts index c552ce43..a90f2d7d 100644 --- a/src/utils/emitter.ts +++ b/src/utils/emitter.ts @@ -1,5 +1,9 @@ import mitt, { Emitter } from 'mitt' +export enum EMITTER_EVENTS { + +} + const emitter: Emitter = mitt() export default emitter \ No newline at end of file diff --git a/src/views/Editor/Canvas/AlignmentLine.vue b/src/views/Editor/Canvas/AlignmentLine.vue index 1311618d..06582b29 100644 --- a/src/views/Editor/Canvas/AlignmentLine.vue +++ b/src/views/Editor/Canvas/AlignmentLine.vue @@ -15,7 +15,7 @@