update
This commit is contained in:
parent
ded04bf837
commit
bfb24e5055
|
@ -63,4 +63,21 @@ export interface MultiSelectRange {
|
|||
maxX: number;
|
||||
minY: number;
|
||||
maxY: number;
|
||||
}
|
||||
|
||||
export type ImageClipDataRange = [[number, number], [number, number]]
|
||||
|
||||
export interface ImageClipData {
|
||||
range: ImageClipDataRange;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ImageClipedEmitData {
|
||||
range: ImageClipDataRange;
|
||||
position: {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
|
@ -25,21 +25,13 @@ export default defineComponent({
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
offsetX: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
offsetY: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const left = computed(() => props.axis.x * canvasScale.value + props.offsetX + 'px')
|
||||
const top = computed(() => props.axis.y * canvasScale.value + props.offsetY + 'px')
|
||||
const left = computed(() => props.axis.x * canvasScale.value + 'px')
|
||||
const top = computed(() => props.axis.y * canvasScale.value + 'px')
|
||||
|
||||
const sizeStyle = computed(() => {
|
||||
if(props.type === 'vertical') return { height: props.length * canvasScale.value + 'px' }
|
||||
|
@ -66,11 +58,11 @@ export default defineComponent({
|
|||
border: 0 dashed $themeColor;
|
||||
|
||||
&.vertical {
|
||||
margin-left: -0.5px;
|
||||
transform: translateY(-0.5px);
|
||||
border-left-width: 1px;
|
||||
}
|
||||
&.horizontal {
|
||||
margin-top: -0.5px;
|
||||
transform: translateX(-0.5px);
|
||||
border-top-width: 1px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,26 +50,10 @@
|
|||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, onUnmounted, PropType, reactive, ref } from 'vue'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
import { ImageClipData, ImageClipDataRange, ImageClipedEmitData } from '@/types/edit'
|
||||
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
|
||||
type ClipDataRange = [[number, number], [number, number]]
|
||||
|
||||
interface ClipData {
|
||||
range: ClipDataRange;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ClipedEmitData {
|
||||
range: ClipDataRange;
|
||||
position: {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
type ScaleClipRangeType = 't-l' | 't-r' | 'b-l' | 'b-r'
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -83,7 +67,7 @@ export default defineComponent({
|
|||
required: true,
|
||||
},
|
||||
clipData: {
|
||||
type: Object as PropType<ClipData>,
|
||||
type: Object as PropType<ImageClipData>,
|
||||
required: true,
|
||||
},
|
||||
clipPath: {
|
||||
|
@ -123,7 +107,7 @@ export default defineComponent({
|
|||
left: '0',
|
||||
})
|
||||
const isSettingClipRange = ref(false)
|
||||
const currentRange = ref<ClipDataRange | null>(null)
|
||||
const currentRange = ref<ImageClipDataRange | null>(null)
|
||||
|
||||
const getClipDataTransformInfo = () => {
|
||||
const [start, end] = props.clipData ? props.clipData.range : [[0, 0], [100, 100]]
|
||||
|
@ -209,7 +193,7 @@ export default defineComponent({
|
|||
height: (topImgWrapperPosition.height - 100) / 100 * props.height,
|
||||
}
|
||||
|
||||
const clipedEmitData: ClipedEmitData = {
|
||||
const clipedEmitData: ImageClipedEmitData = {
|
||||
range: currentRange.value,
|
||||
position,
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<ImageClipHandler
|
||||
v-if="isCliping"
|
||||
:src="elementInfo.src"
|
||||
:clipData="elementInfo.clip"
|
||||
:canvasScale="canvasScale"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:top="elementInfo.top"
|
||||
:left="elementInfo.left"
|
||||
:clipPath="clipShape.style"
|
||||
@clip="range => clip(range)"
|
||||
/>
|
||||
<div
|
||||
class="image-element-operate"
|
||||
v-else
|
||||
:class="{
|
||||
'selected': isSelected,
|
||||
'multi-select': isMultiSelect && isSelected,
|
||||
'active': isActive,
|
||||
}"
|
||||
>
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
:key="line.type"
|
||||
:type="line.type"
|
||||
:style="line.style"
|
||||
/>
|
||||
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<ResizeHandler
|
||||
class="operate-resize-handler"
|
||||
v-for="point in resizeHandlers"
|
||||
:key="point.direction"
|
||||
:type="point.direction"
|
||||
:style="point.style"
|
||||
@mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
|
||||
/>
|
||||
<RotateHandler
|
||||
class="operate-rotate-handler"
|
||||
:style="{ left: scaleWidth / 2 + 'px' }"
|
||||
@mousedown.stop="rotateElement(elementInfo)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<AnimationIndex v-if="animationIndex !== -1" :animationIndex="animationIndex" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { State } from '@/store'
|
||||
|
||||
import { PPTImageElement } from '@/types/slides'
|
||||
import { OperateResizeHandler, ImageClipedEmitData } from '@/types/edit'
|
||||
import useCommonOperate from '../hooks/useCommonOperate'
|
||||
|
||||
import RotateHandler from './RotateHandler.vue'
|
||||
import ResizeHandler from './ResizeHandler.vue'
|
||||
import BorderLine from './BorderLine.vue'
|
||||
import AnimationIndex from './AnimationIndex.vue'
|
||||
import ImageClipHandler from './ImageClipHandler.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'image-element-operate',
|
||||
components: {
|
||||
RotateHandler,
|
||||
ResizeHandler,
|
||||
BorderLine,
|
||||
AnimationIndex,
|
||||
ImageClipHandler,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTImageElement>,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
animationIndex: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
rotateElement: {
|
||||
type: Function as PropType<(element: PPTImageElement) => void>,
|
||||
required: true,
|
||||
},
|
||||
scaleElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTImageElement, command: OperateResizeHandler) => void>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||
|
||||
const clipingImageElId = ref('')
|
||||
|
||||
const isCliping = computed(() => clipingImageElId.value === props.elementInfo.id)
|
||||
|
||||
const clip = (data: ImageClipedEmitData) => {
|
||||
clipingImageElId.value = ''
|
||||
|
||||
if(!data) return
|
||||
|
||||
const { range, position } = data
|
||||
const originClip = props.elementInfo.clip || {}
|
||||
|
||||
const _props = {
|
||||
clip: { ...originClip, range },
|
||||
left: props.elementInfo.left + position.left,
|
||||
top: props.elementInfo.top + position.top,
|
||||
width: props.elementInfo.width + position.width,
|
||||
height: props.elementInfo.height + position.height,
|
||||
}
|
||||
console.log(_props)
|
||||
}
|
||||
|
||||
return {
|
||||
scaleWidth,
|
||||
resizeHandlers,
|
||||
borderLines,
|
||||
isCliping,
|
||||
clip,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-element-operate {
|
||||
&.selected {
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi-select:not(.selected) .operate-border-line {
|
||||
border-color: rgba($color: $themeColor, $alpha: .3);
|
||||
}
|
||||
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -2,9 +2,8 @@
|
|||
<div
|
||||
class="multi-select-operate"
|
||||
:style="{
|
||||
left: minX + 'px',
|
||||
top: minY + 'px',
|
||||
transform: `scale(${1 / canvasScale})`,
|
||||
left: minX * canvasScale + 'px',
|
||||
top: minY * canvasScale + 'px',
|
||||
}"
|
||||
>
|
||||
<BorderLine v-for="line in borderLines" :key="line.type" :type="line.type" :style="line.style" />
|
||||
|
@ -28,10 +27,10 @@ import { State } from '@/store'
|
|||
import { PPTElement, ElementTypes } from '@/types/slides'
|
||||
import { getElementListRange } from '@/utils/element'
|
||||
import { OperateResizeHandler, MultiSelectRange } from '@/types/edit'
|
||||
import useCommonOperate from '@/views/_common/_element/hooks/useCommonOperate'
|
||||
import useCommonOperate from '../hooks/useCommonOperate'
|
||||
|
||||
import ResizeHandler from '@/views/_common/_operate/ResizeHandler.vue'
|
||||
import BorderLine from '@/views/_common/_operate/BorderLine.vue'
|
||||
import ResizeHandler from './ResizeHandler.vue'
|
||||
import BorderLine from './BorderLine.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'multi-select-operate',
|
||||
|
@ -102,6 +101,6 @@ export default defineComponent({
|
|||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
z-index: 101;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div class="rotate-handler"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'rotate-handler',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rotate-handler {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
top: -25px;
|
||||
margin-left: -5px;
|
||||
border: 1px solid $themeColor;
|
||||
background-color: #fff;
|
||||
border-radius: 1px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,131 @@
|
|||
<template>
|
||||
<div
|
||||
class="text-element-operate"
|
||||
:class="{
|
||||
'selected': isSelected,
|
||||
'multi-select': isMultiSelect && isSelected,
|
||||
'active': isActive,
|
||||
}"
|
||||
>
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
:key="line.type"
|
||||
:type="line.type"
|
||||
:style="line.style"
|
||||
/>
|
||||
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<ResizeHandler
|
||||
class="operate-resize-handler"
|
||||
v-for="point in textElementResizeHandlers"
|
||||
:key="point.direction"
|
||||
:type="point.direction"
|
||||
:style="point.style"
|
||||
@mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
|
||||
/>
|
||||
<RotateHandler
|
||||
class="operate-rotate-handler"
|
||||
:style="{ left: scaleWidth / 2 + 'px' }"
|
||||
@mousedown.stop="rotateElement(elementInfo)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<AnimationIndex v-if="animationIndex !== -1" :animationIndex="animationIndex" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { State } from '@/store'
|
||||
|
||||
import { PPTTextElement } from '@/types/slides'
|
||||
import { OperateResizeHandler } from '@/types/edit'
|
||||
import useCommonOperate from '../hooks/useCommonOperate'
|
||||
|
||||
import RotateHandler from './RotateHandler.vue'
|
||||
import ResizeHandler from './ResizeHandler.vue'
|
||||
import BorderLine from './BorderLine.vue'
|
||||
import AnimationIndex from './AnimationIndex.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'text-element-operate',
|
||||
components: {
|
||||
RotateHandler,
|
||||
ResizeHandler,
|
||||
BorderLine,
|
||||
AnimationIndex,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTTextElement>,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
animationIndex: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
rotateElement: {
|
||||
type: Function as PropType<(element: PPTTextElement) => void>,
|
||||
required: true,
|
||||
},
|
||||
scaleElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTTextElement, command: OperateResizeHandler) => void>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||
|
||||
const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||
|
||||
return {
|
||||
scaleWidth,
|
||||
textElementResizeHandlers,
|
||||
borderLines,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text-element-operate {
|
||||
&.selected {
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi-select:not(.selected) .operate-border-line {
|
||||
border-color: rgba($color: $themeColor, $alpha: .3);
|
||||
}
|
||||
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<div
|
||||
class="operate"
|
||||
:style="{
|
||||
top: elementInfo.top * canvasScale + 'px',
|
||||
left: elementInfo.left * canvasScale + 'px',
|
||||
transform: `rotate(${elementInfo.rotate}deg)`,
|
||||
'transform-origin': `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="currentOperateComponent"
|
||||
:elementInfo="elementInfo"
|
||||
:isSelected="isSelected"
|
||||
:isActive="isActive"
|
||||
:isActiveGroupElement="isActiveGroupElement"
|
||||
:isMultiSelect="isMultiSelect"
|
||||
:animationIndex="animationIndex"
|
||||
:rotateElement="rotateElement"
|
||||
:scaleElement="scaleElement"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue'
|
||||
import { PPTElement } from '@/types/slides'
|
||||
import { OperateResizeHandler } from '@/types/edit'
|
||||
|
||||
import ImageElementOperate from './ImageElementOperate.vue'
|
||||
import TextElementOperate from './TextElementOperate.vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { State } from '@/store'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'operate',
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTElement>,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
animationIndex: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
rotateElement: {
|
||||
type: Function as PropType<(element: PPTElement) => void>,
|
||||
required: true,
|
||||
},
|
||||
scaleElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: OperateResizeHandler) => void>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const currentOperateComponent = computed(() => {
|
||||
const elementTypeMap = {
|
||||
'image': ImageElementOperate,
|
||||
'text': TextElementOperate,
|
||||
}
|
||||
return elementTypeMap[props.elementInfo.type] || null
|
||||
})
|
||||
|
||||
return {
|
||||
currentOperateComponent,
|
||||
canvasScale,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.operate {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
|
@ -13,6 +13,13 @@ export default (width: Ref<number>, height: Ref<number>) => {
|
|||
{ direction: OperateResizeHandlers.BOTTOM, style: {left: width.value / 2 + 'px', top: height.value + 'px'} },
|
||||
{ direction: OperateResizeHandlers.RIGHT_BOTTOM, style: {left: width.value + 'px', top: height.value + 'px'} },
|
||||
]
|
||||
}
|
||||
)
|
||||
const textElementResizeHandlers = computed(() => {
|
||||
return [
|
||||
{ direction: OperateResizeHandlers.LEFT, style: {top: height.value / 2 + 'px'} },
|
||||
{ direction: OperateResizeHandlers.RIGHT, style: {left: width.value + 'px', top: height.value / 2 + 'px'} },
|
||||
]
|
||||
})
|
||||
|
||||
const borderLines = computed(() => {
|
||||
|
@ -26,6 +33,7 @@ export default (width: Ref<number>, height: Ref<number>) => {
|
|||
|
||||
return {
|
||||
resizeHandlers,
|
||||
textElementResizeHandlers,
|
||||
borderLines,
|
||||
}
|
||||
}
|
|
@ -216,21 +216,21 @@ export default (
|
|||
targetTop = targetTop - (targetMinY - value)
|
||||
isHorizontalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({type: 'horizontal', axis: {x: min - 20, y: value}, length: max - min + 40})
|
||||
_alignmentLines.push({type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100})
|
||||
}
|
||||
if(Math.abs(targetMaxY - value) < sorptionRange) {
|
||||
if(!isHorizontalAdsorbed) {
|
||||
targetTop = targetTop - (targetMaxY - value)
|
||||
isHorizontalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({type: 'horizontal', axis: {x: min - 20, y: value}, length: max - min + 40})
|
||||
_alignmentLines.push({type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100})
|
||||
}
|
||||
if(Math.abs(targetCenterY - value) < sorptionRange) {
|
||||
if(!isHorizontalAdsorbed) {
|
||||
targetTop = targetTop - (targetCenterY - value)
|
||||
isHorizontalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({type: 'horizontal', axis: {x: min - 20, y: value}, length: max - min + 40})
|
||||
_alignmentLines.push({type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100})
|
||||
}
|
||||
}
|
||||
for(let i = 0; i < verticalLines.length; i++) {
|
||||
|
@ -243,21 +243,21 @@ export default (
|
|||
targetLeft = targetLeft - (targetMinX - value)
|
||||
isVerticalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 20}, length: max - min + 40})
|
||||
_alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100})
|
||||
}
|
||||
if(Math.abs(targetMaxX - value) < sorptionRange) {
|
||||
if(!isVerticalAdsorbed) {
|
||||
targetLeft = targetLeft - (targetMaxX - value)
|
||||
isVerticalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 20}, length: max - min + 40})
|
||||
_alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100})
|
||||
}
|
||||
if(Math.abs(targetCenterX - value) < sorptionRange) {
|
||||
if(!isVerticalAdsorbed) {
|
||||
targetLeft = targetLeft - (targetCenterX - value)
|
||||
isVerticalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 20}, length: max - min + 40})
|
||||
_alignmentLines.push({type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100})
|
||||
}
|
||||
}
|
||||
alignmentLines.value = _alignmentLines
|
||||
|
|
|
@ -192,7 +192,7 @@ export default (
|
|||
correctionVal.offsetY = currentY - value
|
||||
isHorizontalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({type: 'horizontal', axis: {x: min - 20, y: value}, length: max - min + 40})
|
||||
_alignmentLines.push({ type: 'horizontal', axis: {x: min - 50, y: value}, length: max - min + 100 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ export default (
|
|||
correctionVal.offsetX = currentX - value
|
||||
isVerticalAdsorbed = true
|
||||
}
|
||||
_alignmentLines.push({ type: 'vertical', axis: {x: value, y: min - 20}, length: max - min + 40 })
|
||||
_alignmentLines.push({ type: 'vertical', axis: {x: value, y: min - 50}, length: max - min + 100 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,37 @@
|
|||
v-contextmenu="contextmenus"
|
||||
v-click-outside="removeEditorAreaFocus"
|
||||
>
|
||||
<AlignmentLine
|
||||
v-for="(line, index) in alignmentLines"
|
||||
:key="index"
|
||||
:type="line.type"
|
||||
:axis="line.axis"
|
||||
:length="line.length"
|
||||
:offsetX="viewportStyles.left"
|
||||
:offsetY="viewportStyles.top"
|
||||
/>
|
||||
<div
|
||||
class="operates"
|
||||
:style="{
|
||||
left: viewportStyles.left + 'px',
|
||||
top: viewportStyles.top + 'px',
|
||||
}"
|
||||
>
|
||||
<AlignmentLine
|
||||
v-for="(line, index) in alignmentLines"
|
||||
:key="index"
|
||||
:type="line.type"
|
||||
:axis="line.axis"
|
||||
:length="line.length"
|
||||
/>
|
||||
<MultiSelectOperate
|
||||
v-if="activeElementIdList.length > 1"
|
||||
:elementList="elementList"
|
||||
:scaleMultiElement="scaleMultiElement"
|
||||
/>
|
||||
<Operate
|
||||
v-for="element in elementList"
|
||||
:key="element.id"
|
||||
:elementInfo="element"
|
||||
:isSelected="activeElementIdList.includes(element.id)"
|
||||
:isActiveGroupElement="activeGroupElementId === element.id"
|
||||
:isMultiSelect="activeElementIdList.length > 1"
|
||||
:rotateElement="rotateElement"
|
||||
:scaleElement="scaleElement"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="viewport"
|
||||
ref="viewportRef"
|
||||
|
@ -35,27 +57,14 @@
|
|||
:height="mouseSelectionState.height"
|
||||
:quadrant="mouseSelectionState.quadrant"
|
||||
/>
|
||||
|
||||
<MultiSelectOperate
|
||||
v-if="activeElementIdList.length > 1"
|
||||
:elementList="elementList"
|
||||
:scaleMultiElement="scaleMultiElement"
|
||||
/>
|
||||
|
||||
<SlideBackground />
|
||||
|
||||
<EditableElement
|
||||
v-for="(element, index) in elementList"
|
||||
:key="element.id"
|
||||
:elementInfo="element"
|
||||
:elementIndex="index + 1"
|
||||
:isSelected="activeElementIdList.includes(element.id)"
|
||||
:isActive="element.id === handleElementId"
|
||||
:isActiveGroupElement="activeGroupElementId === element.id"
|
||||
:isMultiSelect="activeElementIdList.length > 1"
|
||||
:selectElement="selectElement"
|
||||
:rotateElement="rotateElement"
|
||||
:scaleElement="scaleElement"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -83,11 +92,12 @@ import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
|
|||
import useSelectAllElement from '@/hooks/useSelectAllElement'
|
||||
import useScaleCanvas from '@/hooks/useScaleCanvas'
|
||||
|
||||
import EditableElement from '@/views/_common/_element/EditableElement.vue'
|
||||
import EditableElement from '@/views/_element/EditableElement.vue'
|
||||
import MouseSelection from './MouseSelection.vue'
|
||||
import SlideBackground from './SlideBackground.vue'
|
||||
import MultiSelectOperate from './MultiSelectOperate.vue'
|
||||
import AlignmentLine from './AlignmentLine.vue'
|
||||
import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
|
||||
import Operate from './Operate/index.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editor-canvas',
|
||||
|
@ -95,8 +105,9 @@ export default defineComponent({
|
|||
EditableElement,
|
||||
MouseSelection,
|
||||
SlideBackground,
|
||||
MultiSelectOperate,
|
||||
AlignmentLine,
|
||||
MultiSelectOperate,
|
||||
Operate,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore<State>()
|
||||
|
@ -218,4 +229,7 @@ export default defineComponent({
|
|||
background-color: #fff;
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
.operates {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
|
@ -42,7 +42,7 @@ import { fillDigit } from '@/utils/common'
|
|||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import useSlideHandler from '@/hooks/useSlideHandler'
|
||||
|
||||
import ThumbnailSlide from '@/views/_common/ThumbnailSlide.vue'
|
||||
import ThumbnailSlide from '@/views/ThumbnailSlide.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'thumbnails',
|
||||
|
|
|
@ -23,7 +23,7 @@ import { Slide } from '@/types/slides'
|
|||
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
|
||||
|
||||
import BaseElement from '@/views/_common/_element/BaseElement.vue'
|
||||
import BaseElement from '@/views/_element/BaseElement.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'screen-slide',
|
||||
|
|
|
@ -172,10 +172,10 @@ export default defineComponent({
|
|||
z-index: 2;
|
||||
}
|
||||
&.prev {
|
||||
transform: translateX(-100%);
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
&.next {
|
||||
transform: translateX(100%);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
}
|
||||
.slide-content {
|
||||
|
|
|
@ -30,7 +30,7 @@ import { Slide } from '@/types/slides'
|
|||
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
|
||||
|
||||
import BaseElement from '@/views/_common/_element/BaseElement.vue'
|
||||
import BaseElement from '@/views/_element/BaseElement.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'thumbnail-slide',
|
|
@ -1,223 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
class="editable-element-text"
|
||||
:class="{ 'lock': elementInfo.lock }"
|
||||
:style="{
|
||||
top: elementInfo.top + 'px',
|
||||
left: elementInfo.left + 'px',
|
||||
width: elementInfo.width + 'px',
|
||||
transform: `rotate(${elementInfo.rotate}deg)`,
|
||||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<div class="element-content"
|
||||
:style="{
|
||||
backgroundColor: elementInfo.fill,
|
||||
opacity: elementInfo.opacity,
|
||||
textShadow: shadowStyle,
|
||||
}"
|
||||
v-contextmenu="contextmenus"
|
||||
>
|
||||
<ElementOutline
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<div class="text"
|
||||
v-html="elementInfo.content"
|
||||
:contenteditable="isActive && !elementInfo.lock"
|
||||
@mousedown="$event => handleSelectElement($event, false)"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="operate"
|
||||
:class="{
|
||||
'selected': isSelected,
|
||||
'multi-select': isMultiSelect && isSelected,
|
||||
'active': isActive,
|
||||
}"
|
||||
:style="{ transform: `scale(${1 / canvasScale})` }"
|
||||
>
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
:key="line.type"
|
||||
:type="line.type"
|
||||
:style="line.style"
|
||||
/>
|
||||
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<ResizeHandler
|
||||
class="operate-resize-handler"
|
||||
v-for="point in resizeHandlers"
|
||||
:key="point.direction"
|
||||
:type="point.direction"
|
||||
:style="point.style"
|
||||
@mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
|
||||
/>
|
||||
<RotateHandler
|
||||
class="operate-rotate-handler"
|
||||
:style="{ left: scaleWidth / 2 + 'px' }"
|
||||
@mousedown.stop="rotateElement(elementInfo)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<AnimationIndex v-if="animationIndex !== -1" :animationIndex="animationIndex" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
|
||||
import { PPTTextElement } from '@/types/slides'
|
||||
import { OperateResizeHandler } from '@/types/edit'
|
||||
import useCommonOperate from '@/views/_common/_element/hooks/useCommonOperate'
|
||||
|
||||
import ElementOutline from '@/views/_common/_element/ElementOutline.vue'
|
||||
import RotateHandler from '@/views/_common/_operate/RotateHandler.vue'
|
||||
import ResizeHandler from '@/views/_common/_operate/ResizeHandler.vue'
|
||||
import BorderLine from '@/views/_common/_operate/BorderLine.vue'
|
||||
import AnimationIndex from '@/views/_common/_operate/AnimationIndex.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-element-text',
|
||||
components: {
|
||||
ElementOutline,
|
||||
RotateHandler,
|
||||
ResizeHandler,
|
||||
BorderLine,
|
||||
AnimationIndex,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTTextElement>,
|
||||
required: true,
|
||||
},
|
||||
canvasScale: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
animationIndex: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
selectElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTTextElement, canMove?: boolean) => void>,
|
||||
required: true,
|
||||
},
|
||||
rotateElement: {
|
||||
type: Function as PropType<(element: PPTTextElement) => void>,
|
||||
required: true,
|
||||
},
|
||||
scaleElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTTextElement, command: OperateResizeHandler) => void>,
|
||||
required: true,
|
||||
},
|
||||
contextmenus: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const scaleWidth = computed(() => props.elementInfo.width * props.canvasScale)
|
||||
const scaleHeight = computed(() => props.elementInfo.height * props.canvasScale)
|
||||
|
||||
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||
|
||||
const handleSelectElement = (e: MouseEvent, canMove = true) => {
|
||||
if(props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
props.selectElement(e, props.elementInfo, canMove)
|
||||
}
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
return {
|
||||
scaleWidth,
|
||||
resizeHandlers,
|
||||
borderLines,
|
||||
handleSelectElement,
|
||||
shadowStyle,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editable-element-text {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
|
||||
&.lock .element-content {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.element-content {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
|
||||
.text {
|
||||
position: relative;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.text) {
|
||||
word-break: break-word;
|
||||
font-family: '微软雅黑';
|
||||
outline: 0;
|
||||
|
||||
::selection {
|
||||
background-color: rgba(27, 110, 232, 0.3);
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.operate {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
|
||||
&.selected {
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi-select:not(.selected) .operate-border-line {
|
||||
border-color: rgba($color: $themeColor, $alpha: .3);
|
||||
}
|
||||
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,34 +0,0 @@
|
|||
<template>
|
||||
<div class="rotate-handler">
|
||||
<div class="rotate-icon"><IconFont type="icon-rotate" /></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'rotate-handler',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rotate-handler {
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
margin: -10px 0 0 -10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
box-shadow: 1px 1px 2px #888;
|
||||
|
||||
.rotate-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -7,16 +7,8 @@
|
|||
>
|
||||
<component
|
||||
:is="currentElementComponent"
|
||||
:canvasScale="canvasScale"
|
||||
:elementInfo="elementInfo"
|
||||
:isSelected="isSelected"
|
||||
:isActive="isActive"
|
||||
:isActiveGroupElement="isActiveGroupElement"
|
||||
:isMultiSelect="isMultiSelect"
|
||||
:animationIndex="animationIndex"
|
||||
:selectElement="selectElement"
|
||||
:rotateElement="rotateElement"
|
||||
:scaleElement="scaleElement"
|
||||
:contextmenus="contextmenus"
|
||||
></component>
|
||||
</div>
|
||||
|
@ -24,9 +16,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { State } from '@/store'
|
||||
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement, PPTLineElement } from '@/types/slides'
|
||||
import { PPTElement } from '@/types/slides'
|
||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
|
||||
import useLockElement from '@/hooks/useLockElement'
|
||||
|
@ -36,7 +26,7 @@ import useOrderElement from '@/hooks/useOrderElement'
|
|||
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
||||
import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
|
||||
|
||||
import { ElementOrderCommands, ElementAlignCommands, OperateResizeHandler } from '@/types/edit'
|
||||
import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'
|
||||
|
||||
import ImageElement from './ImageElement/index.vue'
|
||||
import TextElement from './TextElement/index.vue'
|
||||
|
@ -52,43 +42,16 @@ export default defineComponent({
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
animationIndex: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
selectElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove?: boolean) => void>,
|
||||
required: true,
|
||||
},
|
||||
rotateElement: {
|
||||
type: Function as PropType<(element: PPTTextElement | PPTImageElement | PPTShapeElement) => void>,
|
||||
required: true,
|
||||
},
|
||||
scaleElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandler) => void>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const currentElementComponent = computed(() => {
|
||||
const elementTypeMap = {
|
||||
'image': ImageElement,
|
||||
|
@ -172,7 +135,6 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return {
|
||||
canvasScale,
|
||||
currentElementComponent,
|
||||
contextmenus,
|
||||
}
|
|
@ -24,7 +24,7 @@
|
|||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
import useElementOutline from '@/views/_element/hooks/useElementOutline'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'element-outline',
|
|
@ -65,7 +65,7 @@ import ImageRectOutline from './ImageRectOutline.vue'
|
|||
import ImageEllipseOutline from './ImageEllipseOutline.vue'
|
||||
import ImagePolygonOutline from './ImagePolygonOutline.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
import useElementShadow from '@/views/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'base-element-image',
|
|
@ -27,7 +27,7 @@
|
|||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
import useElementOutline from '@/views/_element/hooks/useElementOutline'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'image-ellipse-outline',
|
|
@ -24,7 +24,7 @@
|
|||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
import useElementOutline from '@/views/_element/hooks/useElementOutline'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'image-polygon-outline',
|
|
@ -27,7 +27,7 @@
|
|||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
import useElementOutline from '@/views/_element/hooks/useElementOutline'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'image-rect-outline',
|
|
@ -11,22 +11,8 @@
|
|||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<ImageClip
|
||||
v-if="isCliping"
|
||||
:src="elementInfo.src"
|
||||
:clipData="elementInfo.clip"
|
||||
:canvasScale="canvasScale"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:top="elementInfo.top"
|
||||
:left="elementInfo.left"
|
||||
:clipPath="clipShape.style"
|
||||
@clip="range => clip(range)"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="element-content"
|
||||
v-if="!isCliping"
|
||||
v-contextmenu="contextmenus"
|
||||
:style="{
|
||||
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
|
||||
|
@ -69,74 +55,25 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="operate"
|
||||
:class="{
|
||||
'selected': isSelected,
|
||||
'multi-select': isMultiSelect && isSelected,
|
||||
'active': isActive,
|
||||
}"
|
||||
:style="{ transform: `scale(${1 / canvasScale})` }"
|
||||
v-if="!isCliping"
|
||||
>
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
:key="line.type"
|
||||
:type="line.type"
|
||||
:style="line.style"
|
||||
/>
|
||||
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<ResizeHandler
|
||||
class="operate-resize-handler"
|
||||
v-for="point in resizeHandlers"
|
||||
:key="point.direction"
|
||||
:type="point.direction"
|
||||
:style="point.style"
|
||||
@mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
|
||||
/>
|
||||
<RotateHandler
|
||||
class="operate-rotate-handler"
|
||||
:style="{left: scaleWidth / 2 + 'px'}"
|
||||
@mousedown.stop="rotateElement(elementInfo)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<AnimationIndex v-if="animationIndex !== -1" :animationIndex="animationIndex" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, PropType } from 'vue'
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
|
||||
import { PPTImageElement } from '@/types/slides'
|
||||
import { OperateResizeHandler } from '@/types/edit'
|
||||
import useCommonOperate from '@/views/_common/_element/hooks/useCommonOperate'
|
||||
|
||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
|
||||
import useElementShadow from '@/views/_element/hooks/useElementShadow'
|
||||
|
||||
import RotateHandler from '@/views/_common/_operate/RotateHandler.vue'
|
||||
import ResizeHandler from '@/views/_common/_operate/ResizeHandler.vue'
|
||||
import BorderLine from '@/views/_common/_operate/BorderLine.vue'
|
||||
import AnimationIndex from '@/views/_common/_operate/AnimationIndex.vue'
|
||||
|
||||
import ImageClip, { ClipedEmitData } from './ImageClipHandler.vue'
|
||||
import ImageRectOutline from './ImageRectOutline.vue'
|
||||
import ImageEllipseOutline from './ImageEllipseOutline.vue'
|
||||
import ImagePolygonOutline from './ImagePolygonOutline.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-element-image',
|
||||
components: {
|
||||
RotateHandler,
|
||||
ResizeHandler,
|
||||
BorderLine,
|
||||
AnimationIndex,
|
||||
ImageClip,
|
||||
ImageRectOutline,
|
||||
ImageEllipseOutline,
|
||||
ImagePolygonOutline,
|
||||
|
@ -146,55 +83,29 @@ export default defineComponent({
|
|||
type: Object as PropType<PPTImageElement>,
|
||||
required: true,
|
||||
},
|
||||
canvasScale: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
animationIndex: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
selectElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTImageElement, canMove?: boolean) => void>,
|
||||
required: true,
|
||||
},
|
||||
rotateElement: {
|
||||
type: Function as PropType<(element: PPTImageElement) => void>,
|
||||
required: true,
|
||||
},
|
||||
scaleElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTImageElement, command: OperateResizeHandler) => void>,
|
||||
required: true,
|
||||
},
|
||||
contextmenus: {
|
||||
type: Function,
|
||||
type: Function as PropType<() => ContextmenuItem[]>,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const clipingImageElId = ref('')
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
const scaleWidth = computed(() => props.elementInfo.width * props.canvasScale)
|
||||
const scaleHeight = computed(() => props.elementInfo.height * props.canvasScale)
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
if(props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
props.selectElement(e, props.elementInfo)
|
||||
}
|
||||
const clipShape = computed(() => {
|
||||
if(!props.elementInfo || !props.elementInfo.clip) return CLIPPATHS.rect
|
||||
const shape = props.elementInfo.clip.shape || ClipPathTypes.RECT
|
||||
|
||||
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||
|
||||
const isCliping = computed(() => clipingImageElId.value === props.elementInfo.id)
|
||||
return CLIPPATHS[shape]
|
||||
})
|
||||
|
||||
const imgPosition = computed(() => {
|
||||
if(!props.elementInfo || !props.elementInfo.clip) {
|
||||
|
@ -221,13 +132,6 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
|
||||
const clipShape = computed(() => {
|
||||
if(!props.elementInfo || !props.elementInfo.clip) return CLIPPATHS.rect
|
||||
const shape = props.elementInfo.clip.shape || ClipPathTypes.RECT
|
||||
|
||||
return CLIPPATHS[shape]
|
||||
})
|
||||
|
||||
const filter = computed(() => {
|
||||
if(!props.elementInfo.filters) return ''
|
||||
let filter = ''
|
||||
|
@ -246,45 +150,13 @@ export default defineComponent({
|
|||
return ''
|
||||
})
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
if(isCliping.value || props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
props.selectElement(e, props.elementInfo)
|
||||
}
|
||||
|
||||
const clip = (data: ClipedEmitData) => {
|
||||
clipingImageElId.value = ''
|
||||
|
||||
if(!data) return
|
||||
|
||||
const { range, position } = data
|
||||
const originClip = props.elementInfo.clip || {}
|
||||
|
||||
const _props = {
|
||||
clip: { ...originClip, range },
|
||||
left: props.elementInfo.left + position.left,
|
||||
top: props.elementInfo.top + position.top,
|
||||
width: props.elementInfo.width + position.width,
|
||||
height: props.elementInfo.height + position.height,
|
||||
}
|
||||
console.log(_props)
|
||||
}
|
||||
|
||||
return {
|
||||
scaleWidth,
|
||||
isCliping,
|
||||
imgPosition,
|
||||
clipShape,
|
||||
resizeHandlers,
|
||||
borderLines,
|
||||
filter,
|
||||
flip,
|
||||
shadowStyle,
|
||||
handleSelectElement,
|
||||
clip,
|
||||
clipShape,
|
||||
imgPosition,
|
||||
filter,
|
||||
flip,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -311,35 +183,8 @@ export default defineComponent({
|
|||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.operate {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
|
||||
&.selected {
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi-select:not(.selected) .operate-border-line {
|
||||
border-color: rgba($color: $themeColor, $alpha: .3);
|
||||
}
|
||||
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -28,9 +28,9 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue'
|
||||
import { PPTTextElement } from '@/types/slides'
|
||||
import ElementOutline from '@/views/_common/_element/ElementOutline.vue'
|
||||
import ElementOutline from '@/views/_element/ElementOutline.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
import useElementShadow from '@/views/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'base-element-text',
|
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div
|
||||
class="editable-element-text"
|
||||
:class="{ 'lock': elementInfo.lock }"
|
||||
:style="{
|
||||
top: elementInfo.top + 'px',
|
||||
left: elementInfo.left + 'px',
|
||||
width: elementInfo.width + 'px',
|
||||
transform: `rotate(${elementInfo.rotate}deg)`,
|
||||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<div class="element-content"
|
||||
:style="{
|
||||
backgroundColor: elementInfo.fill,
|
||||
opacity: elementInfo.opacity,
|
||||
textShadow: shadowStyle,
|
||||
}"
|
||||
v-contextmenu="contextmenus"
|
||||
>
|
||||
<ElementOutline
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<div class="text"
|
||||
v-html="elementInfo.content"
|
||||
:contenteditable="!elementInfo.lock"
|
||||
@mousedown="$event => handleSelectElement($event, false)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
import { PPTTextElement } from '@/types/slides'
|
||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import useElementShadow from '@/views/_element/hooks/useElementShadow'
|
||||
|
||||
import ElementOutline from '@/views/_element/ElementOutline.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-element-text',
|
||||
components: {
|
||||
ElementOutline,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTTextElement>,
|
||||
required: true,
|
||||
},
|
||||
selectElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTTextElement, canMove?: boolean) => void>,
|
||||
required: true,
|
||||
},
|
||||
contextmenus: {
|
||||
type: Function as PropType<() => ContextmenuItem[]>,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const handleSelectElement = (e: MouseEvent, canMove = true) => {
|
||||
if(props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
props.selectElement(e, props.elementInfo, canMove)
|
||||
}
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
return {
|
||||
handleSelectElement,
|
||||
shadowStyle,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editable-element-text {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
|
||||
&.lock .element-content {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.element-content {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
|
||||
.text {
|
||||
position: relative;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.text) {
|
||||
word-break: break-word;
|
||||
font-family: '微软雅黑';
|
||||
outline: 0;
|
||||
|
||||
::selection {
|
||||
background-color: rgba(27, 110, 232, 0.3);
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue