This commit is contained in:
pipipi-pikachu 2020-12-22 17:28:14 +08:00
parent fdbb6dde16
commit 5f20da11b3
27 changed files with 561 additions and 506 deletions

348
package-lock.json generated
View File

@ -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",

View File

@ -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

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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: [] })
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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 })
}

View File

@ -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 {

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

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

View File

@ -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
},

View File

@ -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;
}

View File

@ -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
}

View File

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

View File

@ -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',

View File

@ -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: {

View File

@ -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[]>,

View File

@ -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>()

View File

@ -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 {

View File

@ -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,
}
},

View File

@ -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;
}

View File

@ -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
}

View File

@ -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 [
{

View File

@ -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) => {

View File

@ -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,
},
]
}