|
@@ -36,6 +36,13 @@
|
|
|
:type="line.type" :axis="line.axis" :length="line.length"
|
|
:type="line.type" :axis="line.axis" :length="line.length"
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
|
|
+ <MultiSelectOperate
|
|
|
|
|
+ v-if="activeElementIdList.length > 1"
|
|
|
|
|
+ :activeElementList="activeElementList"
|
|
|
|
|
+ :canvasScale="canvasScale"
|
|
|
|
|
+ :scaleMultiElement="scaleMultiElement"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
<EditableElement
|
|
<EditableElement
|
|
|
v-for="(element, index) in elementList"
|
|
v-for="(element, index) in elementList"
|
|
|
:key="element.elId"
|
|
:key="element.elId"
|
|
@@ -63,13 +70,17 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
<script lang="ts">
|
|
|
-import { computed, defineComponent, reactive, ref, watch } from 'vue'
|
|
|
|
|
|
|
+import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue'
|
|
|
import { useStore } from 'vuex'
|
|
import { useStore } from 'vuex'
|
|
|
|
|
+import uniq from 'lodash/uniq'
|
|
|
import { State } from '@/store/state'
|
|
import { State } from '@/store/state'
|
|
|
import { MutationTypes } from '@/store/constants'
|
|
import { MutationTypes } from '@/store/constants'
|
|
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
|
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
|
|
import { getImageDataURL } from '@/utils/image'
|
|
import { getImageDataURL } from '@/utils/image'
|
|
|
|
|
+import { getElementRange } from './utils/elementRange'
|
|
|
|
|
+
|
|
|
|
|
+import { PPTElement } from '@/types/slides'
|
|
|
|
|
|
|
|
import useDropImage from '@/hooks/useDropImage'
|
|
import useDropImage from '@/hooks/useDropImage'
|
|
|
import useSetViewportSize from './hooks/useSetViewportSize'
|
|
import useSetViewportSize from './hooks/useSetViewportSize'
|
|
@@ -77,6 +88,7 @@ import useSetViewportSize from './hooks/useSetViewportSize'
|
|
|
import EditableElement from '@/views/_common/_element/EditableElement.vue'
|
|
import EditableElement from '@/views/_common/_element/EditableElement.vue'
|
|
|
import MouseSelection from './MouseSelection.vue'
|
|
import MouseSelection from './MouseSelection.vue'
|
|
|
import SlideBackground from './SlideBackground.vue'
|
|
import SlideBackground from './SlideBackground.vue'
|
|
|
|
|
+import MultiSelectOperate from './MultiSelectOperate.vue'
|
|
|
import AlignmentLine, { AlignmentLineProps } from './AlignmentLine.vue'
|
|
import AlignmentLine, { AlignmentLineProps } from './AlignmentLine.vue'
|
|
|
|
|
|
|
|
export default defineComponent({
|
|
export default defineComponent({
|
|
@@ -85,22 +97,29 @@ export default defineComponent({
|
|
|
EditableElement,
|
|
EditableElement,
|
|
|
MouseSelection,
|
|
MouseSelection,
|
|
|
SlideBackground,
|
|
SlideBackground,
|
|
|
|
|
+ MultiSelectOperate,
|
|
|
AlignmentLine,
|
|
AlignmentLine,
|
|
|
},
|
|
},
|
|
|
setup() {
|
|
setup() {
|
|
|
const store = useStore<State>()
|
|
const store = useStore<State>()
|
|
|
- const elementList = computed(() => {
|
|
|
|
|
- const currentSlide = store.getters.currentSlide
|
|
|
|
|
- return currentSlide ? JSON.parse(JSON.stringify(currentSlide.elements)) : []
|
|
|
|
|
- })
|
|
|
|
|
|
|
+
|
|
|
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
|
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
|
|
|
|
+ const activeElementList = computed(() => store.getters.activeElementList)
|
|
|
const handleElementId = computed(() => store.state.handleElementId)
|
|
const handleElementId = computed(() => store.state.handleElementId)
|
|
|
- const activeGroupElementId = ref('')
|
|
|
|
|
|
|
+ const ctrlOrShiftKeyActive = computed(() => store.getters.ctrlOrShiftKeyActive)
|
|
|
|
|
|
|
|
|
|
+ const activeGroupElementId = ref('')
|
|
|
const viewportRef = ref<HTMLElement | null>(null)
|
|
const viewportRef = ref<HTMLElement | null>(null)
|
|
|
const isShowGridLines = ref(false)
|
|
const isShowGridLines = ref(false)
|
|
|
const alignmentLines = ref<AlignmentLineProps[]>([])
|
|
const alignmentLines = ref<AlignmentLineProps[]>([])
|
|
|
|
|
+
|
|
|
const currentSlide = computed(() => store.getters.currentSlide)
|
|
const currentSlide = computed(() => store.getters.currentSlide)
|
|
|
|
|
+ const elementList = ref<PPTElement[]>([])
|
|
|
|
|
+ const setLocalElementList = () => {
|
|
|
|
|
+ elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
|
|
|
|
|
+ }
|
|
|
|
|
+ onMounted(setLocalElementList)
|
|
|
|
|
+ watch(currentSlide, setLocalElementList)
|
|
|
|
|
|
|
|
const dropImageFile = useDropImage(viewportRef)
|
|
const dropImageFile = useDropImage(viewportRef)
|
|
|
watch(dropImageFile, () => {
|
|
watch(dropImageFile, () => {
|
|
@@ -180,6 +199,67 @@ export default defineComponent({
|
|
|
document.onmouseup = null
|
|
document.onmouseup = null
|
|
|
isMouseDown = false
|
|
isMouseDown = false
|
|
|
|
|
|
|
|
|
|
+ // 计算当前页面中的每一个元素是否处在鼠标选择范围中(必须完全包裹)
|
|
|
|
|
+ // 将选择范围中的元素添加为激活元素
|
|
|
|
|
+ let inRangeElementList: PPTElement[] = []
|
|
|
|
|
+ for(let i = 0; i < elementList.value.length; i++) {
|
|
|
|
|
+ const element = elementList.value[i]
|
|
|
|
|
+ const mouseSelectionLeft = mouseSelectionState.left
|
|
|
|
|
+ const mouseSelectionTop = mouseSelectionState.top
|
|
|
|
|
+ const mouseSelectionWidth = mouseSelectionState.width
|
|
|
|
|
+ const mouseSelectionHeight = mouseSelectionState.height
|
|
|
|
|
+
|
|
|
|
|
+ const quadrant = mouseSelectionState.quadrant
|
|
|
|
|
+
|
|
|
|
|
+ const { minX, maxX, minY, maxY } = getElementRange(element)
|
|
|
|
|
+
|
|
|
|
|
+ let isInclude = false
|
|
|
|
|
+ if(quadrant === 4) {
|
|
|
|
|
+ isInclude = minX > mouseSelectionLeft &&
|
|
|
|
|
+ maxX < mouseSelectionLeft + mouseSelectionWidth &&
|
|
|
|
|
+ minY > mouseSelectionTop &&
|
|
|
|
|
+ maxY < mouseSelectionTop + mouseSelectionHeight
|
|
|
|
|
+ }
|
|
|
|
|
+ else if(quadrant === 1) {
|
|
|
|
|
+ isInclude = minX > (mouseSelectionLeft - mouseSelectionWidth) &&
|
|
|
|
|
+ maxX < (mouseSelectionLeft - mouseSelectionWidth) + mouseSelectionWidth &&
|
|
|
|
|
+ minY > (mouseSelectionTop - mouseSelectionHeight) &&
|
|
|
|
|
+ maxY < (mouseSelectionTop - mouseSelectionHeight) + mouseSelectionHeight
|
|
|
|
|
+ }
|
|
|
|
|
+ else if(quadrant === 2) {
|
|
|
|
|
+ isInclude = minX > mouseSelectionLeft &&
|
|
|
|
|
+ maxX < mouseSelectionLeft + mouseSelectionWidth &&
|
|
|
|
|
+ minY > (mouseSelectionTop - mouseSelectionHeight) &&
|
|
|
|
|
+ maxY < (mouseSelectionTop - mouseSelectionHeight) + mouseSelectionHeight
|
|
|
|
|
+ }
|
|
|
|
|
+ else if(quadrant === 3) {
|
|
|
|
|
+ isInclude = minX > (mouseSelectionLeft - mouseSelectionWidth) &&
|
|
|
|
|
+ maxX < (mouseSelectionLeft - mouseSelectionWidth) + mouseSelectionWidth &&
|
|
|
|
|
+ minY > mouseSelectionTop &&
|
|
|
|
|
+ maxY < mouseSelectionTop + mouseSelectionHeight
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 被锁定的元素除外
|
|
|
|
|
+ if(isInclude && !element.isLock) inRangeElementList.push(element)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 对于组合元素成员,必须所有成员都在选择范围中才算被选中
|
|
|
|
|
+ inRangeElementList = inRangeElementList.filter(inRangeElement => {
|
|
|
|
|
+ if(inRangeElement.groupId) {
|
|
|
|
|
+ const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.elId)
|
|
|
|
|
+ const groupElementList = elementList.value.filter(element => element.groupId === inRangeElement.groupId)
|
|
|
|
|
+ return groupElementList.every(groupElement => inRangeElementIdList.includes(groupElement.elId))
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+ })
|
|
|
|
|
+ const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.elId)
|
|
|
|
|
+
|
|
|
|
|
+ // 原本就存在激活元素(可能需要清空),或者本次选择了至少一个元素(可能需要选择),才会具体更新激活元素状态
|
|
|
|
|
+ // 否则不做多余的激活元素状态更新(原本就没有激活元素,本次也没有选择任何元素,只是点击了一下空白区域,状态为:空 -> 空)
|
|
|
|
|
+ if(activeElementIdList.value.length > 0 || inRangeElementIdList.length) {
|
|
|
|
|
+ store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, inRangeElementIdList)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
mouseSelectionState.isShow = false
|
|
mouseSelectionState.isShow = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -187,7 +267,7 @@ export default defineComponent({
|
|
|
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
|
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
|
|
|
|
|
|
|
const handleClickBlankArea = (e: MouseEvent) => {
|
|
const handleClickBlankArea = (e: MouseEvent) => {
|
|
|
- updateMouseSelection(e)
|
|
|
|
|
|
|
+ if(!ctrlOrShiftKeyActive.value) updateMouseSelection(e)
|
|
|
if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
|
if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -195,8 +275,76 @@ export default defineComponent({
|
|
|
if(editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
|
|
if(editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const selectElement = () => {
|
|
|
|
|
- console.log('selectElement')
|
|
|
|
|
|
|
+ const moveElement = (e: MouseEvent, element: PPTElement) => {
|
|
|
|
|
+ console.log(e, element)
|
|
|
|
|
+ }
|
|
|
|
|
+ const selectElement = (e: MouseEvent, element: PPTElement, canMove = true) => {
|
|
|
|
|
+ if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
|
|
|
|
+
|
|
|
|
|
+ // 如果被点击的元素处于未激活状态,则将他设置为激活元素(单选),或者加入到激活元素中(多选)
|
|
|
|
|
+ if(!activeElementIdList.value.includes(element.elId)) {
|
|
|
|
|
+ let newActiveIdList: string[] = []
|
|
|
|
|
+
|
|
|
|
|
+ if(ctrlOrShiftKeyActive.value) {
|
|
|
|
|
+ newActiveIdList = [...activeElementIdList.value, element.elId]
|
|
|
|
|
+ }
|
|
|
|
|
+ else newActiveIdList = [element.elId]
|
|
|
|
|
+
|
|
|
|
|
+ // 同时如果该元素是分组成员,需要将和他同组的元素一起激活
|
|
|
|
|
+ if(element.groupId) {
|
|
|
|
|
+ const groupMembersId: string[] = []
|
|
|
|
|
+ elementList.value.forEach((el: PPTElement) => {
|
|
|
|
|
+ if(el.groupId === element.groupId) groupMembersId.push(el.elId)
|
|
|
|
|
+ })
|
|
|
|
|
+ newActiveIdList = [...newActiveIdList, ...groupMembersId]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, uniq(newActiveIdList))
|
|
|
|
|
+ store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.elId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果被点击的元素已激活,且按下了多选按钮,则取消其激活状态(除非该元素或分组是最后的一个激活元素)
|
|
|
|
|
+ else if(ctrlOrShiftKeyActive.value) {
|
|
|
|
|
+ let newActiveIdList: string[] = []
|
|
|
|
|
+
|
|
|
|
|
+ // 同时如果该元素是分组成员,需要将和他同组的元素一起取消
|
|
|
|
|
+ if(element.groupId) {
|
|
|
|
|
+ const groupMembersId: string[] = []
|
|
|
|
|
+ elementList.value.forEach((el: PPTElement) => {
|
|
|
|
|
+ if(el.groupId === element.groupId) groupMembersId.push(el.elId)
|
|
|
|
|
+ })
|
|
|
|
|
+ newActiveIdList = activeElementIdList.value.filter(elId => !groupMembersId.includes(elId))
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ newActiveIdList = activeElementIdList.value.filter(elId => elId !== element.elId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(newActiveIdList.length > 0) {
|
|
|
|
|
+ store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, newActiveIdList)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果被点击的元素已激活,且没有按下多选按钮,且该元素不是当前操作元素,则将其设置为当前操作元素
|
|
|
|
|
+ else if(handleElementId.value !== element.elId) {
|
|
|
|
|
+ store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.elId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ else if(activeGroupElementId.value !== element.elId && element.groupId) {
|
|
|
|
|
+ const startPageX = e.pageX
|
|
|
|
|
+ const startPageY = e.pageY
|
|
|
|
|
+
|
|
|
|
|
+ ;(e.target as HTMLElement).onmouseup = (e: MouseEvent) => {
|
|
|
|
|
+ const currentPageX = e.pageX
|
|
|
|
|
+ const currentPageY = e.pageY
|
|
|
|
|
+
|
|
|
|
|
+ if(startPageX === currentPageX && startPageY === currentPageY) {
|
|
|
|
|
+ activeGroupElementId.value = element.elId
|
|
|
|
|
+ ;(e.target as HTMLElement).onmouseup = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(canMove) moveElement(e, element)
|
|
|
}
|
|
}
|
|
|
const rotateElement = () => {
|
|
const rotateElement = () => {
|
|
|
console.log('rotateElement')
|
|
console.log('rotateElement')
|
|
@@ -204,6 +352,9 @@ export default defineComponent({
|
|
|
const scaleElement = () => {
|
|
const scaleElement = () => {
|
|
|
console.log('scaleElement')
|
|
console.log('scaleElement')
|
|
|
}
|
|
}
|
|
|
|
|
+ const scaleMultiElement = () => {
|
|
|
|
|
+ console.log('scaleMultiElement')
|
|
|
|
|
+ }
|
|
|
const orderElement = () => {
|
|
const orderElement = () => {
|
|
|
console.log('orderElement')
|
|
console.log('orderElement')
|
|
|
}
|
|
}
|
|
@@ -248,6 +399,7 @@ export default defineComponent({
|
|
|
return {
|
|
return {
|
|
|
elementList,
|
|
elementList,
|
|
|
activeElementIdList,
|
|
activeElementIdList,
|
|
|
|
|
+ activeElementList,
|
|
|
handleElementId,
|
|
handleElementId,
|
|
|
activeGroupElementId,
|
|
activeGroupElementId,
|
|
|
canvasRef,
|
|
canvasRef,
|
|
@@ -263,6 +415,7 @@ export default defineComponent({
|
|
|
selectElement,
|
|
selectElement,
|
|
|
rotateElement,
|
|
rotateElement,
|
|
|
scaleElement,
|
|
scaleElement,
|
|
|
|
|
+ scaleMultiElement,
|
|
|
orderElement,
|
|
orderElement,
|
|
|
combineElements,
|
|
combineElements,
|
|
|
uncombineElements,
|
|
uncombineElements,
|