添加选择范围插入元素
This commit is contained in:
parent
071c2b30fb
commit
9fdbf35fa1
|
@ -2,7 +2,7 @@
|
||||||
<div class="file-input" @click="handleClick()">
|
<div class="file-input" @click="handleClick()">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<input
|
<input
|
||||||
class="file-input"
|
class="input"
|
||||||
type="file"
|
type="file"
|
||||||
name="upload"
|
name="upload"
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
|
@ -46,7 +46,7 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.file-input {
|
.input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,13 +1,6 @@
|
||||||
const DEFAULT_COLOR = '#41464b'
|
const DEFAULT_COLOR = '#41464b'
|
||||||
|
|
||||||
export const DEFAULT_TEXT = {
|
export const DEFAULT_TEXT = {
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
width: 300,
|
|
||||||
height: 0,
|
|
||||||
opacity: 1,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
segmentSpacing: 5,
|
|
||||||
content: '请输入内容',
|
content: '请输入内容',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +17,6 @@ export const DEFAULT_SHAPE = {
|
||||||
|
|
||||||
export const DEFAULT_LINE = {
|
export const DEFAULT_LINE = {
|
||||||
style: 'solid',
|
style: 'solid',
|
||||||
points: ['', ''],
|
|
||||||
width: 4,
|
width: 4,
|
||||||
color: DEFAULT_COLOR,
|
color: DEFAULT_COLOR,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
export const LINES = [
|
export interface LinePoolItem {
|
||||||
|
path: string;
|
||||||
|
style: string;
|
||||||
|
points: [string, string];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LINE_LIST = [
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', ''] },
|
{ path: 'M0,0 L20,20', style: 'solid', points: ['', ''] },
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'arrow'] },
|
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['arrow', 'arrow'] },
|
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'cusp'] },
|
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['cusp', 'cusp'] },
|
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'dot'] },
|
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['dot', 'dot'] },
|
|
||||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', ''] },
|
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', ''] },
|
||||||
|
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'arrow'] },
|
||||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', 'arrow'] },
|
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', 'arrow'] },
|
||||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['arrow', 'arrow'] },
|
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'dot'] },
|
||||||
]
|
]
|
|
@ -0,0 +1,127 @@
|
||||||
|
export interface ShapePoolItem {
|
||||||
|
viewBox: number;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SHAPE_LIST = [
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 200 L 0 0 L 150 0 L 200 50 L 200 200 L 0 200'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 150 L 0 0 L 150 0 L 200 50 L 200 200 L 50 200 L 0 150'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 20 0 L 180 0 Q 200 0 200 20 L 200 180 Q 200 200 180 200 L 20 200 Q 0 200 0 180 L 0 20 Q 0 0 20 0 '
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 0 L 140 0 Q 200 0 200 60 L 200 200 L 60 200 Q 0 200 0 140 L 0 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 0 L 140 0 Q 200 0 200 60 L 200 200 L 0 200 L 0 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 200 0 Q 0 0 0 200 L 200 200 L 200 0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 A 100 100 0 1 1 0 100 L 100 100 L 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 L 0 200 L 200 200 L 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 0 L 0 200 L 200 200 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 50 0 L 200 0 L 150 200 L 0 200 L 50 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 L 0 100 L 100 200 L 200 100 L 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 50 0 L 150 0 L 200 200 L 0 200 L 50 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 L 0 90 L 50 200 L 150 200 L 200 90 L 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 L 0 60 L 0 140 L 100 200 L 200 140 L 200 60 L 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 60 0 L 140 0 L 200 60 L 200 140 L 140 200 L 60 200 L 0 140 L 0 60 L 60 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 A 100 100 0 1 1 0 100 L 0 0 L 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 A 50 50 0 1 0 200 120 A 100 100 0 1 1 100 0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 L 122 70 L 196 70 L 136 114 L 158 182 L 100 140 L 42 182 L 64 114 L 4 70 L 78 70 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 0 L 0 100 L 50 100 L 50 200 L 150 200 L 150 100 L 200 100 L 100 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 100 200 L 200 100 L 150 100 L 150 0 L 50 0 L 50 100 L 0 100 L 100 200 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 200 150 L 100 150 L 100 200 L 0 100 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 0 150 L 100 150 L 100 200 L 200 100 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 0 L 120 0 L 200 100 L 120 200 L 0 200 L 80 100 L 0 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 80 0 L 200 0 L 120 100 L 200 200 L 80 200 L 0 100 L 80 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 0 0 L 140 0 L 200 100 L 140 200 L 0 200 L 0 100 L 0 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 60 0 L 200 0 L 200 100 L 200 200 L 60 200 L 0 100 L 60 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 70 0 L 70 70 L 0 70 L 0 130 L 70 130 L 70 200 L 130 200 L 130 130 L 200 130 L 200 70 L 130 70 L 130 0 L 70 0 Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewBox: 200,
|
||||||
|
path: 'M 40 0 L 0 40 L 60 100 L 0 160 L 40 200 L 100 140 L 160 200 L 200 160 L 140 100 L 200 40 L 160 0 L 100 60 L 40 0 Z'
|
||||||
|
},
|
||||||
|
]
|
|
@ -4,6 +4,8 @@ import { createRandomCode } from '@/utils/common'
|
||||||
import { getImageSize } from '@/utils/image'
|
import { getImageSize } from '@/utils/image'
|
||||||
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||||
import { PPTElement, TableElementCell } from '@/types/slides'
|
import { PPTElement, TableElementCell } from '@/types/slides'
|
||||||
|
import { ShapePoolItem } from '@/configs/shapes'
|
||||||
|
import { LinePoolItem } from '@/configs/lines'
|
||||||
import {
|
import {
|
||||||
DEFAULT_IMAGE,
|
DEFAULT_IMAGE,
|
||||||
DEFAULT_TEXT,
|
DEFAULT_TEXT,
|
||||||
|
@ -33,6 +35,8 @@ export default () => {
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const createElement = (element: PPTElement) => {
|
const createElement = (element: PPTElement) => {
|
||||||
store.commit(MutationTypes.ADD_ELEMENT, element)
|
store.commit(MutationTypes.ADD_ELEMENT, element)
|
||||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [element.id])
|
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [element.id])
|
||||||
|
@ -109,7 +113,7 @@ export default () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const createShapeElement = (position: CommonElementPosition, path: string, viewBox: number) => {
|
const createShapeElement = (position: CommonElementPosition, data: ShapePoolItem) => {
|
||||||
const { left, top, width, height } = position
|
const { left, top, width, height } = position
|
||||||
createElement({
|
createElement({
|
||||||
...DEFAULT_SHAPE,
|
...DEFAULT_SHAPE,
|
||||||
|
@ -119,12 +123,12 @@ export default () => {
|
||||||
top,
|
top,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
viewBox,
|
viewBox: data.viewBox,
|
||||||
path,
|
path: data.path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const createLineElement = (position: LineElementPosition, points: [string, string]) => {
|
const createLineElement = (position: LineElementPosition, data: LinePoolItem) => {
|
||||||
const { left, top, start, end } = position
|
const { left, top, start, end } = position
|
||||||
createElement({
|
createElement({
|
||||||
...DEFAULT_LINE,
|
...DEFAULT_LINE,
|
||||||
|
@ -134,7 +138,7 @@ export default () => {
|
||||||
top,
|
top,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
points,
|
points: data.points,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ export enum MutationTypes {
|
||||||
SET_EDITORAREA_FOCUS = 'setEditorAreaFocus',
|
SET_EDITORAREA_FOCUS = 'setEditorAreaFocus',
|
||||||
SET_DISABLE_HOTKEYS_STATE = 'setDisableHotkeysState',
|
SET_DISABLE_HOTKEYS_STATE = 'setDisableHotkeysState',
|
||||||
SET_GRID_LINES_STATE = 'setGridLinesState',
|
SET_GRID_LINES_STATE = 'setGridLinesState',
|
||||||
SET_CREATING_ELEMENT_TYPE = 'setCreatingElementType',
|
SET_CREATING_ELEMENT = 'setCreatingElement',
|
||||||
SET_AVAILABLE_FONTS = 'setAvailableFonts',
|
SET_AVAILABLE_FONTS = 'setAvailableFonts',
|
||||||
SET_TOOLBAR_STATE = 'setToolbarState',
|
SET_TOOLBAR_STATE = 'setToolbarState',
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { mutations } from './mutations'
|
||||||
import { MutationTypes, ActionTypes } from './constants'
|
import { MutationTypes, ActionTypes } from './constants'
|
||||||
|
|
||||||
import { Slide } from '@/types/slides'
|
import { Slide } from '@/types/slides'
|
||||||
|
import { CreatingElement } from '@/types/edit'
|
||||||
import { ToolbarState } from '@/types/toolbar'
|
import { ToolbarState } from '@/types/toolbar'
|
||||||
import { slides } from '@/mocks/index'
|
import { slides } from '@/mocks/index'
|
||||||
import { FontName } from '@/configs/fontName'
|
import { FontName } from '@/configs/fontName'
|
||||||
|
@ -20,7 +21,7 @@ export interface State {
|
||||||
editorAreaFocus: boolean;
|
editorAreaFocus: boolean;
|
||||||
disableHotkeys: boolean;
|
disableHotkeys: boolean;
|
||||||
showGridLines: boolean;
|
showGridLines: boolean;
|
||||||
creatingElementType: string;
|
creatingElement: CreatingElement | null;
|
||||||
availableFonts: FontName[];
|
availableFonts: FontName[];
|
||||||
toolbarState: ToolbarState;
|
toolbarState: ToolbarState;
|
||||||
slides: Slide[];
|
slides: Slide[];
|
||||||
|
@ -41,7 +42,7 @@ const state: State = {
|
||||||
editorAreaFocus: false,
|
editorAreaFocus: false,
|
||||||
disableHotkeys: false,
|
disableHotkeys: false,
|
||||||
showGridLines: false,
|
showGridLines: false,
|
||||||
creatingElementType: '',
|
creatingElement: null,
|
||||||
availableFonts: [],
|
availableFonts: [],
|
||||||
toolbarState: 'slideStyle',
|
toolbarState: 'slideStyle',
|
||||||
slides: slides,
|
slides: slides,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { MutationTree } from 'vuex'
|
||||||
import { MutationTypes } from './constants'
|
import { MutationTypes } from './constants'
|
||||||
import { State } from './index'
|
import { State } from './index'
|
||||||
import { Slide, PPTElement } from '@/types/slides'
|
import { Slide, PPTElement } from '@/types/slides'
|
||||||
|
import { CreatingElement } from '@/types/edit'
|
||||||
import { FONT_NAMES } from '@/configs/fontName'
|
import { FONT_NAMES } from '@/configs/fontName'
|
||||||
import { isSupportFontFamily } from '@/utils/fontFamily'
|
import { isSupportFontFamily } from '@/utils/fontFamily'
|
||||||
|
|
||||||
|
@ -49,8 +50,8 @@ export const mutations: MutationTree<State> = {
|
||||||
state.showGridLines = show
|
state.showGridLines = show
|
||||||
},
|
},
|
||||||
|
|
||||||
[MutationTypes.SET_CREATING_ELEMENT_TYPE](state, type: string) {
|
[MutationTypes.SET_CREATING_ELEMENT](state, element: CreatingElement | null) {
|
||||||
state.creatingElementType = type
|
state.creatingElement = element
|
||||||
},
|
},
|
||||||
|
|
||||||
[MutationTypes.SET_AVAILABLE_FONTS](state) {
|
[MutationTypes.SET_AVAILABLE_FONTS](state) {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { ShapePoolItem } from '@/configs/shapes'
|
||||||
|
import { LinePoolItem } from '@/configs/lines'
|
||||||
|
|
||||||
export type ElementOrderCommand = 'up' | 'down' | 'top' | 'bottom'
|
export type ElementOrderCommand = 'up' | 'down' | 'top' | 'bottom'
|
||||||
|
|
||||||
export enum ElementOrderCommands {
|
export enum ElementOrderCommands {
|
||||||
|
@ -85,4 +88,17 @@ export interface ImageClipedEmitData {
|
||||||
export interface CreateElementSelectionData {
|
export interface CreateElementSelectionData {
|
||||||
start: [number, number];
|
start: [number, number];
|
||||||
end: [number, number];
|
end: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreatingTextElement {
|
||||||
|
type: 'text';
|
||||||
|
}
|
||||||
|
export interface CreatingShapeElement {
|
||||||
|
type: 'shape';
|
||||||
|
data: ShapePoolItem;
|
||||||
|
}
|
||||||
|
export interface CreatingLineElement {
|
||||||
|
type: 'line';
|
||||||
|
data: LinePoolItem;
|
||||||
|
}
|
||||||
|
export type CreatingElement = CreatingTextElement | CreatingShapeElement | CreatingLineElement
|
|
@ -33,6 +33,8 @@ export interface PPTTextElement {
|
||||||
rotate?: number;
|
rotate?: number;
|
||||||
outline?: PPTElementOutline;
|
outline?: PPTElementOutline;
|
||||||
fill?: string;
|
fill?: string;
|
||||||
|
lineHeight?: number;
|
||||||
|
wordSpace?: number;
|
||||||
opacity?: number;
|
opacity?: number;
|
||||||
shadow?: PPTElementShadow;
|
shadow?: PPTElementShadow;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
ref="selectionRef"
|
ref="selectionRef"
|
||||||
@mousedown.stop="$event => createSelection($event)"
|
@mousedown.stop="$event => createSelection($event)"
|
||||||
>
|
>
|
||||||
<div :class="['selection', elementType]" v-if="start && end" :style="position">
|
<div :class="['selection', creatingElement.type]" v-if="start && end" :style="position">
|
||||||
|
|
||||||
<!-- 绘制线条专用 -->
|
<!-- 绘制线条专用 -->
|
||||||
<SvgWrapper
|
<SvgWrapper
|
||||||
v-if="elementType === 'line' && lineData"
|
v-if="creatingElement.type === 'line' && lineData"
|
||||||
overflow="visible"
|
overflow="visible"
|
||||||
:width="lineData.svgWidth"
|
:width="lineData.svgWidth"
|
||||||
:height="lineData.svgHeight"
|
:height="lineData.svgHeight"
|
||||||
|
@ -42,7 +42,7 @@ export default defineComponent({
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const store = useStore<State>()
|
const store = useStore<State>()
|
||||||
const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
|
const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
|
||||||
const elementType = computed(() => store.state.creatingElementType)
|
const creatingElement = computed(() => store.state.creatingElement)
|
||||||
|
|
||||||
const start = ref<[number, number] | null>(null)
|
const start = ref<[number, number] | null>(null)
|
||||||
const end = ref<[number, number] | null>(null)
|
const end = ref<[number, number] | null>(null)
|
||||||
|
@ -67,7 +67,7 @@ export default defineComponent({
|
||||||
start.value = [startPageX, startPageY]
|
start.value = [startPageX, startPageY]
|
||||||
|
|
||||||
document.onmousemove = e => {
|
document.onmousemove = e => {
|
||||||
if(!isMouseDown) return
|
if(!creatingElement.value || !isMouseDown) return
|
||||||
|
|
||||||
let currentPageX = e.pageX
|
let currentPageX = e.pageX
|
||||||
let currentPageY = e.pageY
|
let currentPageY = e.pageY
|
||||||
|
@ -79,7 +79,7 @@ export default defineComponent({
|
||||||
const absX = Math.abs(moveX)
|
const absX = Math.abs(moveX)
|
||||||
const absY = Math.abs(moveY)
|
const absY = Math.abs(moveY)
|
||||||
|
|
||||||
if(elementType.value === 'shape') {
|
if(creatingElement.value.type === 'shape') {
|
||||||
// moveX和moveY一正一负
|
// moveX和moveY一正一负
|
||||||
const isOpposite = (moveY > 0 && moveX < 0) || (moveY < 0 && moveX > 0)
|
const isOpposite = (moveY > 0 && moveX < 0) || (moveY < 0 && moveX > 0)
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(elementType.value === 'line') {
|
else if(creatingElement.value.type === 'line') {
|
||||||
if(absX > absY) currentPageY = startPageY
|
if(absX > absY) currentPageY = startPageY
|
||||||
else currentPageX = startPageX
|
else currentPageX = startPageX
|
||||||
}
|
}
|
||||||
|
@ -115,13 +115,14 @@ export default defineComponent({
|
||||||
start: start.value,
|
start: start.value,
|
||||||
end: end.value,
|
end: end.value,
|
||||||
})
|
})
|
||||||
store.commit(MutationTypes.SET_CREATING_ELEMENT_TYPE, '')
|
|
||||||
}
|
}
|
||||||
|
else store.commit(MutationTypes.SET_CREATING_ELEMENT, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineData = computed(() => {
|
const lineData = computed(() => {
|
||||||
if(!start.value || !end.value || elementType.value !== 'line') return null
|
if(!start.value || !end.value) return null
|
||||||
|
if(!creatingElement.value || creatingElement.value.type !== 'line') return null
|
||||||
|
|
||||||
const [_startX, _startY] = start.value
|
const [_startX, _startY] = start.value
|
||||||
const [_endX, _endY] = end.value
|
const [_endX, _endY] = end.value
|
||||||
|
@ -176,7 +177,7 @@ export default defineComponent({
|
||||||
selectionRef,
|
selectionRef,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
elementType,
|
creatingElement,
|
||||||
createSelection,
|
createSelection,
|
||||||
lineData,
|
lineData,
|
||||||
position,
|
position,
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { computed, Ref } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { MutationTypes, State } from '@/store'
|
||||||
|
import { CreateElementSelectionData, CreatingLineElement, CreatingShapeElement } from '@/types/edit'
|
||||||
|
import useCreateElement from '@/hooks/useCreateElement'
|
||||||
|
|
||||||
|
export default (viewportRef: Ref<HTMLElement | null>) => {
|
||||||
|
const store = useStore<State>()
|
||||||
|
const canvasScale = computed(() => store.state.canvasScale)
|
||||||
|
const creatingElement = computed(() => store.state.creatingElement)
|
||||||
|
|
||||||
|
const formatCreateSelection = (selectionData: CreateElementSelectionData) => {
|
||||||
|
const { start, end } = selectionData
|
||||||
|
|
||||||
|
if(!viewportRef.value) return
|
||||||
|
const viewportRect = viewportRef.value.getBoundingClientRect()
|
||||||
|
|
||||||
|
const [startX, startY] = start
|
||||||
|
const [endX, endY] = end
|
||||||
|
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 left = (minX - viewportRect.x) / canvasScale.value
|
||||||
|
const top = (minY - viewportRect.y) / canvasScale.value
|
||||||
|
const width = (maxX - minX) / canvasScale.value
|
||||||
|
const height = (maxY - minY) / canvasScale.value
|
||||||
|
|
||||||
|
return { left, top, width, height }
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatCreateSelectionForLine = (selectionData: CreateElementSelectionData) => {
|
||||||
|
const { start, end } = selectionData
|
||||||
|
|
||||||
|
if(!viewportRef.value) return
|
||||||
|
const viewportRect = viewportRef.value.getBoundingClientRect()
|
||||||
|
|
||||||
|
const [startX, startY] = start
|
||||||
|
const [endX, endY] = end
|
||||||
|
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 left = (minX - viewportRect.x) / canvasScale.value
|
||||||
|
const top = (minY - viewportRect.y) / canvasScale.value
|
||||||
|
const width = (maxX - minX) / canvasScale.value
|
||||||
|
const height = (maxY - minY) / canvasScale.value
|
||||||
|
|
||||||
|
const _start: [number, number] = [
|
||||||
|
startX === minX ? 0 : width,
|
||||||
|
startY === minY ? 0 : height,
|
||||||
|
]
|
||||||
|
const _end: [number, number] = [
|
||||||
|
endX === minX ? 0 : width,
|
||||||
|
endY === minY ? 0 : height,
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
start: _start,
|
||||||
|
end: _end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { createTextElement, createShapeElement, createLineElement } = useCreateElement()
|
||||||
|
|
||||||
|
const insertElementFromCreateSelection = (selectionData: CreateElementSelectionData) => {
|
||||||
|
if(!creatingElement.value) return
|
||||||
|
|
||||||
|
const type = creatingElement.value.type
|
||||||
|
if(type === 'text') {
|
||||||
|
const position = formatCreateSelection(selectionData)
|
||||||
|
position && createTextElement(position)
|
||||||
|
}
|
||||||
|
else if(type === 'shape') {
|
||||||
|
const position = formatCreateSelection(selectionData)
|
||||||
|
position && createShapeElement(position, (creatingElement.value as CreatingShapeElement).data)
|
||||||
|
}
|
||||||
|
else if(type === 'line') {
|
||||||
|
const position = formatCreateSelectionForLine(selectionData)
|
||||||
|
position && createLineElement(position, (creatingElement.value as CreatingLineElement).data)
|
||||||
|
}
|
||||||
|
store.commit(MutationTypes.SET_CREATING_ELEMENT, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
insertElementFromCreateSelection,
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,8 @@
|
||||||
v-click-outside="removeEditorAreaFocus"
|
v-click-outside="removeEditorAreaFocus"
|
||||||
>
|
>
|
||||||
<ElementCreateSelection
|
<ElementCreateSelection
|
||||||
v-if="creatingElementType"
|
v-if="creatingElement"
|
||||||
@created="data => createElement(data)"
|
@created="data => insertElementFromCreateSelection(data)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="viewport-wrapper"
|
class="viewport-wrapper"
|
||||||
|
@ -81,7 +81,7 @@ import throttle from 'lodash/throttle'
|
||||||
import { State, MutationTypes } from '@/store'
|
import { State, MutationTypes } from '@/store'
|
||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
import { PPTElement, Slide } from '@/types/slides'
|
import { PPTElement, Slide } from '@/types/slides'
|
||||||
import { AlignmentLineProps, CreateElementSelectionData } from '@/types/edit'
|
import { AlignmentLineProps } from '@/types/edit'
|
||||||
import { removeAllRanges } from '@/utils/selection'
|
import { removeAllRanges } from '@/utils/selection'
|
||||||
|
|
||||||
import useViewportSize from './hooks/useViewportSize'
|
import useViewportSize from './hooks/useViewportSize'
|
||||||
|
@ -92,6 +92,7 @@ import useScaleElement from './hooks/useScaleElement'
|
||||||
import useSelectElement from './hooks/useSelectElement'
|
import useSelectElement from './hooks/useSelectElement'
|
||||||
import useDragElement from './hooks/useDragElement'
|
import useDragElement from './hooks/useDragElement'
|
||||||
import useDragLineElement from './hooks/useDragLineElement'
|
import useDragLineElement from './hooks/useDragLineElement'
|
||||||
|
import useInsertFromCreateSelection from './hooks/useInsertFromCreateSelection'
|
||||||
|
|
||||||
import useDeleteElement from '@/hooks/useDeleteElement'
|
import useDeleteElement from '@/hooks/useDeleteElement'
|
||||||
import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
|
import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
|
||||||
|
@ -185,10 +186,8 @@ export default defineComponent({
|
||||||
store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
|
store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const creatingElementType = computed(() => store.state.creatingElementType)
|
const creatingElement = computed(() => store.state.creatingElement)
|
||||||
const createElement = (data: CreateElementSelectionData) => {
|
const { insertElementFromCreateSelection } = useInsertFromCreateSelection(viewportRef)
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextmenus = (): ContextmenuItem[] => {
|
const contextmenus = (): ContextmenuItem[] => {
|
||||||
return [
|
return [
|
||||||
|
@ -230,8 +229,8 @@ export default defineComponent({
|
||||||
removeEditorAreaFocus,
|
removeEditorAreaFocus,
|
||||||
currentSlide,
|
currentSlide,
|
||||||
isShowGridLines,
|
isShowGridLines,
|
||||||
creatingElementType,
|
creatingElement,
|
||||||
createElement,
|
insertElementFromCreateSelection,
|
||||||
alignmentLines,
|
alignmentLines,
|
||||||
selectElement,
|
selectElement,
|
||||||
rotateElement,
|
rotateElement,
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div class="line-pool">
|
||||||
|
<div class="line-item" v-for="(line, index) in lineList" :key="index">
|
||||||
|
<div class="line-content" @click="selectLine(line)">
|
||||||
|
<SvgWrapper
|
||||||
|
overflow="visible"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<LinePointMarker
|
||||||
|
v-if="line.points[0]"
|
||||||
|
:id="`preset-line-${index}`"
|
||||||
|
position="start"
|
||||||
|
:type="line.points[0]"
|
||||||
|
color="#aaa"
|
||||||
|
:baseSize="2"
|
||||||
|
/>
|
||||||
|
<LinePointMarker
|
||||||
|
v-if="line.points[1]"
|
||||||
|
:id="`preset-line-${index}`"
|
||||||
|
position="end"
|
||||||
|
:type="line.points[1]"
|
||||||
|
color="#999"
|
||||||
|
:baseSize="2"
|
||||||
|
/>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
:d="line.path"
|
||||||
|
stroke="#aaa"
|
||||||
|
fill="none"
|
||||||
|
stroke-width="2"
|
||||||
|
:stroke-dasharray="line.style === 'solid' ? '0, 0' : '4, 1'"
|
||||||
|
stroke-linecap
|
||||||
|
stroke-linejoin
|
||||||
|
stroke-miterlimit
|
||||||
|
:marker-start="line.points[0] ? `url(#${`preset-line-${index}`}-${line.points[0]}-start)` : ''"
|
||||||
|
:marker-end="line.points[1] ? `url(#${`preset-line-${index}`}-${line.points[1]}-end)` : ''"
|
||||||
|
></path>
|
||||||
|
</SvgWrapper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { LINE_LIST, LinePoolItem } from '@/configs/lines'
|
||||||
|
|
||||||
|
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||||
|
import LinePointMarker from '@/views/components/element/LineElement/LinePointMarker.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'line-pool',
|
||||||
|
components: {
|
||||||
|
SvgWrapper,
|
||||||
|
LinePointMarker,
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const lineList = LINE_LIST
|
||||||
|
|
||||||
|
const selectLine = (line: LinePoolItem) => {
|
||||||
|
emit('select', line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lineList,
|
||||||
|
selectLine,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.line-pool {
|
||||||
|
width: 200px;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
|
||||||
|
@include grid-layout-wrapper();
|
||||||
|
}
|
||||||
|
.line-item {
|
||||||
|
@include grid-layout-item(5, 19%);
|
||||||
|
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 19%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.line-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<ul class="shape-pool">
|
||||||
|
<li class="shape-item" v-for="(shape, index) in shapeList" :key="index">
|
||||||
|
<div class="shape-content" @click="selectShape(shape)">
|
||||||
|
<SvgWrapper
|
||||||
|
overflow="visible"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
:transform="`scale(${20 / shape.viewBox}, ${20 / shape.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-miterlimit="8"
|
||||||
|
stroke-linejoin
|
||||||
|
fill="transparent"
|
||||||
|
stroke="#999"
|
||||||
|
stroke-width="2"
|
||||||
|
:d="shape.path"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</SvgWrapper>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { SHAPE_LIST, ShapePoolItem } from '@/configs/shapes'
|
||||||
|
|
||||||
|
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'shape-pool',
|
||||||
|
components: {
|
||||||
|
SvgWrapper,
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const shapeList = SHAPE_LIST
|
||||||
|
|
||||||
|
const selectShape = (shape: ShapePoolItem) => {
|
||||||
|
emit('select', shape)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shapeList,
|
||||||
|
selectShape,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.shape-pool {
|
||||||
|
width: 400px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow: auto;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
|
||||||
|
@include grid-layout-wrapper();
|
||||||
|
}
|
||||||
|
.shape-item {
|
||||||
|
@include grid-layout-item(10, 9%);
|
||||||
|
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 9%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.shape-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,12 +6,22 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="add-element-handler">
|
<div class="add-element-handler">
|
||||||
<FontSizeOutlined class="handler-item" @click="createElement('text')" />
|
<FontSizeOutlined class="handler-item" @click="drawText()" />
|
||||||
<FileInput @change="files => insertImageElement(files)">
|
<FileInput @change="files => insertImageElement(files)">
|
||||||
<PictureOutlined class="handler-item" />
|
<PictureOutlined class="handler-item" />
|
||||||
</FileInput>
|
</FileInput>
|
||||||
<StarOutlined class="handler-item" @click="createElement('shape')" />
|
<Popover trigger="click">
|
||||||
<LineOutlined class="handler-item" @click="createElement('line')" />
|
<template v-slot:content>
|
||||||
|
<ShapePool @select="shape => drawShape(shape)" />
|
||||||
|
</template>
|
||||||
|
<StarOutlined class="handler-item" />
|
||||||
|
</Popover>
|
||||||
|
<Popover trigger="click">
|
||||||
|
<template v-slot:content>
|
||||||
|
<LinePool @select="line => drawLine(line)" />
|
||||||
|
</template>
|
||||||
|
<LineOutlined class="handler-item" />
|
||||||
|
</Popover>
|
||||||
<TableOutlined class="handler-item" />
|
<TableOutlined class="handler-item" />
|
||||||
<PieChartOutlined class="handler-item" />
|
<PieChartOutlined class="handler-item" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,11 +39,16 @@ import { defineComponent, computed } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { MutationTypes, State } from '@/store'
|
import { MutationTypes, State } from '@/store'
|
||||||
import { getImageDataURL } from '@/utils/image'
|
import { getImageDataURL } from '@/utils/image'
|
||||||
|
import { ShapePoolItem } from '@/configs/shapes'
|
||||||
|
import { LinePoolItem } from '@/configs/lines'
|
||||||
import useScaleCanvas from '@/hooks/useScaleCanvas'
|
import useScaleCanvas from '@/hooks/useScaleCanvas'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
import useCreateElement from '@/hooks/useCreateElement'
|
import useCreateElement from '@/hooks/useCreateElement'
|
||||||
|
|
||||||
|
import ShapePool from './ShapePool.vue'
|
||||||
|
import LinePool from './LinePool.vue'
|
||||||
import FileInput from '@/components/FileInput.vue'
|
import FileInput from '@/components/FileInput.vue'
|
||||||
|
import { Popover } from 'ant-design-vue'
|
||||||
import {
|
import {
|
||||||
UndoOutlined,
|
UndoOutlined,
|
||||||
RedoOutlined,
|
RedoOutlined,
|
||||||
|
@ -50,6 +65,8 @@ import {
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'canvas-tool',
|
name: 'canvas-tool',
|
||||||
components: {
|
components: {
|
||||||
|
ShapePool,
|
||||||
|
LinePool,
|
||||||
FileInput,
|
FileInput,
|
||||||
UndoOutlined,
|
UndoOutlined,
|
||||||
RedoOutlined,
|
RedoOutlined,
|
||||||
|
@ -61,6 +78,7 @@ export default defineComponent({
|
||||||
PieChartOutlined,
|
PieChartOutlined,
|
||||||
MinusOutlined,
|
MinusOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
|
Popover,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<State>()
|
const store = useStore<State>()
|
||||||
|
@ -81,8 +99,25 @@ export default defineComponent({
|
||||||
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
|
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
const createElement = (type: string) => {
|
const drawText = () => {
|
||||||
store.commit(MutationTypes.SET_CREATING_ELEMENT_TYPE, type)
|
store.commit(MutationTypes.SET_CREATING_ELEMENT, {
|
||||||
|
type: 'text',
|
||||||
|
data: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawShape = (shape: ShapePoolItem) => {
|
||||||
|
store.commit(MutationTypes.SET_CREATING_ELEMENT, {
|
||||||
|
type: 'shape',
|
||||||
|
data: shape,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawLine = (line: LinePoolItem) => {
|
||||||
|
store.commit(MutationTypes.SET_CREATING_ELEMENT, {
|
||||||
|
type: 'line',
|
||||||
|
data: line,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -93,7 +128,9 @@ export default defineComponent({
|
||||||
redo,
|
redo,
|
||||||
undo,
|
undo,
|
||||||
insertImageElement,
|
insertImageElement,
|
||||||
createElement,
|
drawText,
|
||||||
|
drawShape,
|
||||||
|
drawLine,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue