添加创建元素区域

This commit is contained in:
pipipi-pikachu 2020-12-27 20:31:46 +08:00
parent 8fb6e53342
commit 0988cbae90
8 changed files with 244 additions and 8 deletions

View File

@ -9,6 +9,7 @@ export enum MutationTypes {
SET_EDITORAREA_FOCUS = 'setEditorAreaFocus',
SET_DISABLE_HOTKEYS_STATE = 'setDisableHotkeysState',
SET_GRID_LINES_STATE = 'setGridLinesState',
SET_CREATING_ELEMENT_TYPE = 'setCreatingElementType',
SET_AVAILABLE_FONTS = 'setAvailableFonts',
SET_TOOLBAR_STATE = 'setToolbarState',

View File

@ -20,6 +20,7 @@ export interface State {
editorAreaFocus: boolean;
disableHotkeys: boolean;
showGridLines: boolean;
creatingElementType: string;
availableFonts: FontName[];
toolbarState: ToolbarState;
slides: Slide[];
@ -40,6 +41,7 @@ const state: State = {
editorAreaFocus: false,
disableHotkeys: false,
showGridLines: false,
creatingElementType: '',
availableFonts: [],
toolbarState: 'slideStyle',
slides: slides,

View File

@ -49,6 +49,10 @@ export const mutations: MutationTree<State> = {
state.showGridLines = show
},
[MutationTypes.SET_CREATING_ELEMENT_TYPE](state, type: string) {
state.creatingElementType = type
},
[MutationTypes.SET_AVAILABLE_FONTS](state) {
state.availableFonts = FONT_NAMES.filter(font => isSupportFontFamily(font.en))
},

View File

@ -80,4 +80,9 @@ export interface ImageClipedEmitData {
width: number;
height: number;
};
}
export interface CreateElementSelectionData {
start: [number, number];
end: [number, number];
}

View File

@ -0,0 +1,205 @@
<template>
<div
class="element-create-selection"
ref="selectionRef"
@mousedown.stop="$event => createSelection($event)"
>
<div :class="['selection', elementType]" v-if="start && end" :style="position">
<!-- 绘制线条专用 -->
<SvgWrapper
v-if="elementType === 'line' && lineData"
overflow="visible"
:width="lineData.svgWidth"
:height="lineData.svgHeight"
>
<path
:d="lineData.path"
stroke="#888"
fill="none"
stroke-width="1"
stroke-linecap
stroke-linejoin
stroke-miterlimit
></path>
</SvgWrapper>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, reactive, Ref, ref } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, State } from '@/store'
import SvgWrapper from '@/components/SvgWrapper.vue'
export default defineComponent({
name: 'element-create-selection',
components: {
SvgWrapper,
},
setup(props, { emit }) {
const store = useStore<State>()
const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
const elementType = computed(() => store.state.creatingElementType)
const start = ref<[number, number] | null>(null)
const end = ref<[number, number] | null>(null)
const selectionRef = ref<HTMLElement | null>(null)
const offset = reactive({
x: 0,
y: 0,
})
onMounted(() => {
if(!selectionRef.value) return
const { x, y } = selectionRef.value.getBoundingClientRect()
offset.x = x
offset.y = y
})
const createSelection = (e: MouseEvent) => {
let isMouseDown = true
const startPageX = e.pageX
const startPageY = e.pageY
start.value = [startPageX, startPageY]
document.onmousemove = e => {
if(!isMouseDown) return
let currentPageX = e.pageX
let currentPageY = e.pageY
if(ctrlOrShiftKeyActive.value) {
const moveX = currentPageX - startPageX
const moveY = currentPageY - startPageY
const absX = Math.abs(moveX)
const absY = Math.abs(moveY)
if(elementType.value === 'shape') {
// moveXmoveY
const isOpposite = (moveY > 0 && moveX < 0) || (moveY < 0 && moveX > 0)
if(absX > absY) {
currentPageY = isOpposite ? startPageY - moveX : startPageY + moveX
}
else {
currentPageX = isOpposite ? startPageX - moveY : startPageX + moveY
}
}
else if(elementType.value === 'line') {
if(absX > absY) currentPageY = startPageY
else currentPageX = startPageX
}
}
end.value = [currentPageX, currentPageY]
}
document.onmouseup = e => {
document.onmousemove = null
document.onmouseup = null
isMouseDown = false
const endPageX = e.pageX
const endPageY = e.pageY
const minSize = 30
if(Math.abs(endPageX - startPageX) >= minSize || Math.abs(endPageY - startPageY) >= minSize) {
emit('created', {
start: start.value,
end: end.value,
})
store.commit(MutationTypes.SET_CREATING_ELEMENT_TYPE, '')
}
}
}
const lineData = computed(() => {
if(!start.value || !end.value || elementType.value !== 'line') return null
const [_startX, _startY] = start.value
const [_endX, _endY] = end.value
const minX = Math.min(_startX, _endX)
const maxX = Math.max(_startX, _endX)
const minY = Math.min(_startY, _endY)
const maxY = Math.max(_startY, _endY)
const svgWidth = maxX - minX >= 24 ? maxX - minX : 24
const svgHeight = maxY - minY >= 24 ? maxY - minY : 24
const startX = _startX === minX ? 0 : maxX - minX
const startY = _startY === minY ? 0 : maxY - minY
const endX = _endX === minX ? 0 : maxX - minX
const endY = _endY === minY ? 0 : maxY - minY
const path = `M${startX}, ${startY} L${endX}, ${endY}`
return {
svgWidth,
svgHeight,
startX,
startY,
endX,
endY,
path,
}
})
const position = computed(() => {
if(!start.value || !end.value) return {}
const [startX, startY] = start.value
const [endX, endY] = end.value
const minX = Math.min(startX, endX)
const maxX = Math.max(startX, endX)
const minY = Math.min(startY, endY)
const maxY = Math.max(startY, endY)
const width = maxX - minX
const height = maxY - minY
return {
left: minX - offset.x + 'px',
top: minY - offset.y + 'px',
width: width + 'px',
height: height + 'px',
}
})
return {
selectionRef,
start,
end,
elementType,
createSelection,
lineData,
position,
}
},
})
</script>
<style lang="scss" scoped>
.element-create-selection {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
cursor: crosshair;
}
.selection {
position: absolute;
&:not(.line) {
border: 1px solid #888;
}
}
</style>

View File

@ -7,6 +7,10 @@
v-contextmenu="contextmenus"
v-click-outside="removeEditorAreaFocus"
>
<ElementCreateSelection
v-if="creatingElementType"
@created="data => createElement(data)"
/>
<div
class="viewport-wrapper"
:style="{
@ -77,7 +81,7 @@ import throttle from 'lodash/throttle'
import { State, MutationTypes } from '@/store'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import { PPTElement, Slide } from '@/types/slides'
import { AlignmentLineProps } from '@/types/edit'
import { AlignmentLineProps, CreateElementSelectionData } from '@/types/edit'
import useViewportSize from './hooks/useViewportSize'
import useMouseSelection from './hooks/useMouseSelection'
@ -97,6 +101,7 @@ import EditableElement from './EditableElement.vue'
import MouseSelection from './MouseSelection.vue'
import SlideBackground from './SlideBackground.vue'
import AlignmentLine from './AlignmentLine.vue'
import ElementCreateSelection from './ElementCreateSelection.vue'
import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
import Operate from './Operate/index.vue'
@ -107,6 +112,7 @@ export default defineComponent({
MouseSelection,
SlideBackground,
AlignmentLine,
ElementCreateSelection,
MultiSelectOperate,
Operate,
},
@ -177,6 +183,11 @@ export default defineComponent({
store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
}
const creatingElementType = computed(() => store.state.creatingElementType)
const createElement = (data: CreateElementSelectionData) => {
console.log(data)
}
const contextmenus = (): ContextmenuItem[] => {
return [
{
@ -217,6 +228,8 @@ export default defineComponent({
removeEditorAreaFocus,
currentSlide,
isShowGridLines,
creatingElementType,
createElement,
alignmentLines,
selectElement,
rotateElement,

View File

@ -6,12 +6,12 @@
</div>
<div class="add-element-handler">
<IconFont class="handler-item" type="icon-font-size" />
<IconFont class="handler-item" type="icon-font-size" @click="createElement('text')" />
<UploadInput @change="files => insertImageElement(files)">
<IconFont class="handler-item" type="icon-image" />
</UploadInput>
<IconFont class="handler-item" type="icon-star" />
<IconFont class="handler-item" type="icon-line" />
<IconFont class="handler-item" type="icon-star" @click="createElement('shape')" />
<IconFont class="handler-item" type="icon-line" @click="createElement('line')" />
<IconFont class="handler-item" type="icon-table" />
<IconFont class="handler-item" type="icon-piechart" />
</div>
@ -27,7 +27,7 @@
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store'
import { MutationTypes, State } from '@/store'
import { getImageDataURL } from '@/utils/image'
import useScaleCanvas from '@/hooks/useScaleCanvas'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
@ -59,6 +59,10 @@ export default defineComponent({
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
}
const createElement = (type: string) => {
store.commit(MutationTypes.SET_CREATING_ELEMENT_TYPE, type)
}
return {
scaleCanvas,
canvasScalePercentage,
@ -67,6 +71,7 @@ export default defineComponent({
redo,
undo,
insertImageElement,
createElement,
}
},
})

View File

@ -97,13 +97,14 @@ export default () => {
const { ctrlKey, shiftKey } = e
const key = e.key.toUpperCase()
if(ctrlKey && !ctrlKeyActive.value) store.commit(MutationTypes.SET_CTRL_KEY_STATE, true)
if(shiftKey && !shiftKeyActive.value) store.commit(MutationTypes.SET_SHIFT_KEY_STATE, true)
if(ctrlKey && key === KEYS.F) {
e.preventDefault()
enterScreening()
store.commit(MutationTypes.SET_CTRL_KEY_STATE, false)
}
if(ctrlKey && !ctrlKeyActive.value) store.commit(MutationTypes.SET_CTRL_KEY_STATE, true)
if(shiftKey && !shiftKeyActive.value) store.commit(MutationTypes.SET_SHIFT_KEY_STATE, true)
if(!editorAreaFocus.value && !thumbnailsFocus.value) return