添加选择范围插入元素

This commit is contained in:
pipipi-pikachu 2021-01-03 17:26:02 +08:00
parent 071c2b30fb
commit 9fdbf35fa1
16 changed files with 520 additions and 53 deletions

View File

@ -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>

View File

@ -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,
} }

View File

@ -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'] },
] ]

127
src/configs/shapes.ts Normal file
View File

@ -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'
},
]

View File

@ -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,
}) })
} }

View File

@ -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',

View File

@ -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,

View File

@ -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) {

View File

@ -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

View File

@ -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;
} }

View File

@ -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') {
// moveXmoveY // moveXmoveY
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,

View File

@ -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,
}
}

View File

@ -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,

View File

@ -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>

View File

@ -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>

View File

@ -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,
} }
}, },
}) })