|
|
@@ -1,28 +1,49 @@
|
|
|
<template>
|
|
|
<div class="multi-position-panel">
|
|
|
<ButtonGroup class="row">
|
|
|
- <Button style="flex: 1;">左</Button>
|
|
|
- <Button style="flex: 1;">中</Button>
|
|
|
- <Button style="flex: 1;">右</Button>
|
|
|
+ <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="左对齐">
|
|
|
+ <Button style="flex: 1;" @click="alignActiveElement('left')"><IconFont type="icon-align-left" /></Button>
|
|
|
+ </Tooltip>
|
|
|
+ <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="水平居中">
|
|
|
+ <Button style="flex: 1;" @click="alignActiveElement('horizontal')"><IconFont type="icon-align-vertical-center" /></Button>
|
|
|
+ </Tooltip>
|
|
|
+ <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="右对齐">
|
|
|
+ <Button style="flex: 1;" @click="alignActiveElement('right')"><IconFont type="icon-align-right" /></Button>
|
|
|
+ </Tooltip>
|
|
|
</ButtonGroup>
|
|
|
<ButtonGroup class="row">
|
|
|
- <Button style="flex: 1;">上</Button>
|
|
|
- <Button style="flex: 1;">中</Button>
|
|
|
- <Button style="flex: 1;">下</Button>
|
|
|
+ <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="上对齐">
|
|
|
+ <Button style="flex: 1;" @click="alignActiveElement('top')"><IconFont type="icon-align-top" /></Button>
|
|
|
+ </Tooltip>
|
|
|
+ <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="垂直居中">
|
|
|
+ <Button style="flex: 1;" @click="alignActiveElement('vertical')"><IconFont type="icon-align-horizontal-center" /></Button>
|
|
|
+ </Tooltip>
|
|
|
+ <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下对齐">
|
|
|
+ <Button style="flex: 1;" @click="alignActiveElement('bottom')"><IconFont type="icon-align-bottom" /></Button>
|
|
|
+ </Tooltip>
|
|
|
</ButtonGroup>
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
<ButtonGroup class="row">
|
|
|
- <Button style="flex: 1;">组合</Button>
|
|
|
- <Button style="flex: 1;">取消组合</Button>
|
|
|
+ <Button :disabled="!canCombine" @click="combineElements()" style="flex: 1;">组合</Button>
|
|
|
+ <Button :disabled="canCombine" @click="uncombineElements()" style="flex: 1;">取消组合</Button>
|
|
|
</ButtonGroup>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts">
|
|
|
-import { defineComponent } from 'vue'
|
|
|
-import { Button, Divider } from 'ant-design-vue'
|
|
|
+import { computed, defineComponent, Ref } from 'vue'
|
|
|
+import { useStore } from 'vuex'
|
|
|
+import { MutationTypes, State } from '@/store'
|
|
|
+import { PPTElement, Slide } from '@/types/slides'
|
|
|
+import { ElementAlignCommand, ElementAlignCommands } from '@/types/edit'
|
|
|
+import { getElementListRange } from '@/utils/element'
|
|
|
+import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|
|
+import useCombineElement from '@/hooks/useCombineElement'
|
|
|
+
|
|
|
+import { Button, Divider, Tooltip } from 'ant-design-vue'
|
|
|
+import IconFont from '@/components/IconFont'
|
|
|
|
|
|
export default defineComponent({
|
|
|
name: 'multi-position-panel',
|
|
|
@@ -30,6 +51,138 @@ export default defineComponent({
|
|
|
Button,
|
|
|
ButtonGroup: Button.Group,
|
|
|
Divider,
|
|
|
+ Tooltip,
|
|
|
+ IconFont,
|
|
|
+ },
|
|
|
+ setup() {
|
|
|
+ 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 { addHistorySnapshot } = useHistorySnapshot()
|
|
|
+ const { combineElements, uncombineElements } = useCombineElement()
|
|
|
+
|
|
|
+ const canCombine = computed(() => {
|
|
|
+ const firstGroupId = activeElementList.value[0].groupId
|
|
|
+ if(!firstGroupId) return true
|
|
|
+
|
|
|
+ const inSameGroup = activeElementList.value.every(el => (el.groupId && el.groupId) === firstGroupId)
|
|
|
+ return !inSameGroup
|
|
|
+ })
|
|
|
+
|
|
|
+ const alignActiveElement = (command: ElementAlignCommand) => {
|
|
|
+ const { minX, maxX, minY, maxY } = getElementListRange(activeElementList.value)
|
|
|
+ const elementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
|
|
|
+
|
|
|
+ // 获取每一个组合的宽高位置
|
|
|
+ const groupElementRangeMap = {}
|
|
|
+ for(const activeElement of activeElementList.value) {
|
|
|
+ if(activeElement.groupId && !groupElementRangeMap[activeElement.groupId]) {
|
|
|
+ const groupElements = activeElementList.value.filter(item => item.groupId === activeElement.groupId)
|
|
|
+ groupElementRangeMap[activeElement.groupId] = getElementListRange(groupElements)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(command === ElementAlignCommands.LEFT) {
|
|
|
+ elementList.forEach(element => {
|
|
|
+ if(activeElementIdList.value.includes(element.id)) {
|
|
|
+ if(!element.groupId) element.left = minX
|
|
|
+ else {
|
|
|
+ const range = groupElementRangeMap[element.groupId]
|
|
|
+ const offset = range.minX - minX
|
|
|
+ element.left = element.left - offset
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ else if(command === ElementAlignCommands.RIGHT) {
|
|
|
+ elementList.forEach(element => {
|
|
|
+ if(activeElementIdList.value.includes(element.id)) {
|
|
|
+ if(!element.groupId) {
|
|
|
+ const elWidth = element.type === 'line' ? Math.max(element.start[0], element.end[0]) : element.width
|
|
|
+ element.left = maxX - elWidth
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const range = groupElementRangeMap[element.groupId]
|
|
|
+ const offset = range.maxX - maxX
|
|
|
+ element.left = element.left - offset
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ else if(command === ElementAlignCommands.TOP) {
|
|
|
+ elementList.forEach(element => {
|
|
|
+ if(activeElementIdList.value.includes(element.id)) {
|
|
|
+ if(!element.groupId) element.top = minY
|
|
|
+ else {
|
|
|
+ const range = groupElementRangeMap[element.groupId]
|
|
|
+ const offset = range.minY - minY
|
|
|
+ element.top = element.top - offset
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ else if(command === ElementAlignCommands.BOTTOM) {
|
|
|
+ elementList.forEach(element => {
|
|
|
+ if(activeElementIdList.value.includes(element.id)) {
|
|
|
+ if(!element.groupId) {
|
|
|
+ const elHeight = element.type === 'line' ? Math.max(element.start[1], element.end[1]) : element.height
|
|
|
+ element.top = maxY - elHeight
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const range = groupElementRangeMap[element.groupId]
|
|
|
+ const offset = range.maxY - maxY
|
|
|
+ element.top = element.top - offset
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ else if(command === ElementAlignCommands.HORIZONTAL) {
|
|
|
+ const horizontalCenter = (minX + maxX) / 2
|
|
|
+ elementList.forEach(element => {
|
|
|
+ if(activeElementIdList.value.includes(element.id)) {
|
|
|
+ if(!element.groupId) {
|
|
|
+ const elWidth = element.type === 'line' ? Math.max(element.start[0], element.end[0]) : element.width
|
|
|
+ element.left = horizontalCenter - elWidth / 2
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const range = groupElementRangeMap[element.groupId]
|
|
|
+ const center = (range.maxX + range.minX) / 2
|
|
|
+ const offset = center - horizontalCenter
|
|
|
+ element.left = element.left - offset
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ else if(command === ElementAlignCommands.VERTICAL) {
|
|
|
+ const verticalCenter = (minY + maxY) / 2
|
|
|
+ elementList.forEach(element => {
|
|
|
+ if(activeElementIdList.value.includes(element.id)) {
|
|
|
+ if(!element.groupId) {
|
|
|
+ const elHeight = element.type === 'line' ? Math.max(element.start[1], element.end[1]) : element.height
|
|
|
+ element.top = verticalCenter - elHeight / 2
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const range = groupElementRangeMap[element.groupId]
|
|
|
+ const center = (range.maxY + range.minY) / 2
|
|
|
+ const offset = center - verticalCenter
|
|
|
+ element.top = element.top - offset
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ store.commit(MutationTypes.UPDATE_SLIDE, { elements: elementList })
|
|
|
+ addHistorySnapshot()
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ canCombine,
|
|
|
+ combineElements,
|
|
|
+ uncombineElements,
|
|
|
+ alignActiveElement,
|
|
|
+ }
|
|
|
},
|
|
|
})
|
|
|
</script>
|