update
This commit is contained in:
parent
2d245dd88b
commit
7f301cadfd
|
@ -1,5 +1,4 @@
|
||||||
export enum KEYCODE {
|
export enum KEYCODE {
|
||||||
S = 83,
|
|
||||||
C = 67,
|
C = 67,
|
||||||
X = 88,
|
X = 88,
|
||||||
Z = 90,
|
Z = 90,
|
||||||
|
|
|
@ -9,7 +9,6 @@ export enum MutationTypes {
|
||||||
SET_EDITORAREA_FOCUS = 'setEditorAreaFocus',
|
SET_EDITORAREA_FOCUS = 'setEditorAreaFocus',
|
||||||
SET_DISABLE_HOTKEYS_STATE = 'setDisableHotkeysState',
|
SET_DISABLE_HOTKEYS_STATE = 'setDisableHotkeysState',
|
||||||
SET_AVAILABLE_FONTS = 'setAvailableFonts',
|
SET_AVAILABLE_FONTS = 'setAvailableFonts',
|
||||||
SET_SAVE_STATE = 'setSaveState',
|
|
||||||
|
|
||||||
// slides
|
// slides
|
||||||
SET_SLIDES = 'setSlides',
|
SET_SLIDES = 'setSlides',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { MutationTypes } from './constants'
|
import { MutationTypes } from './constants'
|
||||||
import { State, SaveState } from './state'
|
import { State } from './state'
|
||||||
import { Slide, PPTElement } from '@/types/slides'
|
import { Slide, PPTElement } from '@/types/slides'
|
||||||
import { FONT_NAMES } from '@/configs/fontName'
|
import { FONT_NAMES } from '@/configs/fontName'
|
||||||
import { isSupportFontFamily } from '@/utils/index'
|
import { isSupportFontFamily } from '@/utils/index'
|
||||||
|
@ -36,7 +36,6 @@ export type Mutations = {
|
||||||
[MutationTypes.SET_EDITORAREA_FOCUS](state: State, isFocus: boolean): void;
|
[MutationTypes.SET_EDITORAREA_FOCUS](state: State, isFocus: boolean): void;
|
||||||
[MutationTypes.SET_DISABLE_HOTKEYS_STATE](state: State, disable: boolean): void;
|
[MutationTypes.SET_DISABLE_HOTKEYS_STATE](state: State, disable: boolean): void;
|
||||||
[MutationTypes.SET_AVAILABLE_FONTS](state: State): void;
|
[MutationTypes.SET_AVAILABLE_FONTS](state: State): void;
|
||||||
[MutationTypes.SET_SAVE_STATE](state: State, saveState: SaveState ): void;
|
|
||||||
[MutationTypes.SET_SLIDES](state: State, slides: Slide[]): void;
|
[MutationTypes.SET_SLIDES](state: State, slides: Slide[]): void;
|
||||||
[MutationTypes.ADD_SLIDES](state: State, data: AddSlidesData): void;
|
[MutationTypes.ADD_SLIDES](state: State, data: AddSlidesData): void;
|
||||||
[MutationTypes.SET_SLIDE](state: State, data: SetSlideData): void;
|
[MutationTypes.SET_SLIDE](state: State, data: SetSlideData): void;
|
||||||
|
@ -91,10 +90,6 @@ export const mutations: Mutations = {
|
||||||
state.availableFonts = FONT_NAMES.filter(font => isSupportFontFamily(font.en))
|
state.availableFonts = FONT_NAMES.filter(font => isSupportFontFamily(font.en))
|
||||||
},
|
},
|
||||||
|
|
||||||
[MutationTypes.SET_SAVE_STATE](state, saveState) {
|
|
||||||
state.saveState = saveState
|
|
||||||
},
|
|
||||||
|
|
||||||
// slides
|
// slides
|
||||||
|
|
||||||
[MutationTypes.SET_SLIDES](state, slides) {
|
[MutationTypes.SET_SLIDES](state, slides) {
|
||||||
|
|
|
@ -2,8 +2,6 @@ import { Slide } from '@/types/slides'
|
||||||
import { slides } from '@/mocks/index'
|
import { slides } from '@/mocks/index'
|
||||||
import { FontName } from '@/configs/fontName'
|
import { FontName } from '@/configs/fontName'
|
||||||
|
|
||||||
export type SaveState = 'complete' | 'pending'
|
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
activeElementIdList: string[];
|
activeElementIdList: string[];
|
||||||
handleElementId: string;
|
handleElementId: string;
|
||||||
|
@ -13,7 +11,6 @@ export type State = {
|
||||||
editorAreaFocus: boolean;
|
editorAreaFocus: boolean;
|
||||||
disableHotkeys: boolean;
|
disableHotkeys: boolean;
|
||||||
availableFonts: FontName[];
|
availableFonts: FontName[];
|
||||||
saveState: SaveState;
|
|
||||||
slides: Slide[];
|
slides: Slide[];
|
||||||
slideIndex: number;
|
slideIndex: number;
|
||||||
cursor: number;
|
cursor: number;
|
||||||
|
@ -29,7 +26,6 @@ export const state: State = {
|
||||||
editorAreaFocus: false,
|
editorAreaFocus: false,
|
||||||
disableHotkeys: false,
|
disableHotkeys: false,
|
||||||
availableFonts: [],
|
availableFonts: [],
|
||||||
saveState: 'complete',
|
|
||||||
slides: slides,
|
slides: slides,
|
||||||
slideIndex: 0,
|
slideIndex: 0,
|
||||||
cursor: -1,
|
cursor: -1,
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
export type ElementOrderCommand = 'up' | 'down' | 'top' | 'bottom'
|
||||||
|
|
||||||
|
export enum ElementOrderCommands {
|
||||||
|
UP = 'up',
|
||||||
|
DOWN = 'down',
|
||||||
|
TOP = 'top',
|
||||||
|
BOTTOM = 'bottom',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ElementAlignCommand = 'top'| 'bottom' | 'left' | 'right' | 'vertical' | 'horizontal'
|
||||||
|
|
||||||
|
export enum ElementAlignCommands {
|
||||||
|
TOP = 'top',
|
||||||
|
BOTTOM = 'bottom',
|
||||||
|
LEFT = 'left',
|
||||||
|
RIGHT = 'right',
|
||||||
|
VERTICAL = 'vertical',
|
||||||
|
HORIZONTAL = 'horizontal',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ElementScaleHandler = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
||||||
|
|
||||||
|
export type ElementLockCommand = 'lock' | 'unlock'
|
||||||
|
|
||||||
|
export enum ElementLockCommands {
|
||||||
|
LOCK = 'lock',
|
||||||
|
UNLOCK = 'unlock',
|
||||||
|
}
|
|
@ -19,7 +19,6 @@ export interface PPTElementBorderProps {
|
||||||
|
|
||||||
export interface PPTTextElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
export interface PPTTextElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
||||||
type: 'text';
|
type: 'text';
|
||||||
textType: string;
|
|
||||||
content: string;
|
content: string;
|
||||||
rotate?: number;
|
rotate?: number;
|
||||||
fill?: string;
|
fill?: string;
|
||||||
|
@ -28,20 +27,18 @@ export interface PPTTextElement extends PPTElementBaseProps, PPTElementSizeProps
|
||||||
segmentSpacing?: number;
|
segmentSpacing?: number;
|
||||||
letterSpacing?: number;
|
letterSpacing?: number;
|
||||||
shadow?: string;
|
shadow?: string;
|
||||||
padding?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImageClip {
|
|
||||||
range: [[number, number], [number, number]];
|
|
||||||
shape: string;
|
|
||||||
}
|
|
||||||
export interface PPTImageElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
export interface PPTImageElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
||||||
type: 'image';
|
type: 'image';
|
||||||
lockRatio: boolean;
|
lockRatio: boolean;
|
||||||
imgUrl: string;
|
imgUrl: string;
|
||||||
rotate?: number;
|
rotate?: number;
|
||||||
filter?: string;
|
filter?: string;
|
||||||
clip?: ImageClip;
|
clip?: {
|
||||||
|
range: [[number, number], [number, number]];
|
||||||
|
shape: string;
|
||||||
|
};
|
||||||
flip?: string;
|
flip?: string;
|
||||||
shadow?: string;
|
shadow?: string;
|
||||||
}
|
}
|
||||||
|
@ -58,15 +55,6 @@ export interface PPTShapeElement extends PPTElementBaseProps, PPTElementSizeProp
|
||||||
textAlign?: string;
|
textAlign?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PPTIconElement extends PPTElementBaseProps, PPTElementSizeProps {
|
|
||||||
type: 'icon';
|
|
||||||
color: string;
|
|
||||||
lockRatio: boolean;
|
|
||||||
svgCode: string;
|
|
||||||
rotate?: number;
|
|
||||||
shadow?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PPTLineElement extends PPTElementBaseProps {
|
export interface PPTLineElement extends PPTElementBaseProps {
|
||||||
type: 'line';
|
type: 'line';
|
||||||
start: [number, number];
|
start: [number, number];
|
||||||
|
@ -78,23 +66,11 @@ export interface PPTLineElement extends PPTElementBaseProps {
|
||||||
lineType: string;
|
lineType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BarChartSeries {
|
|
||||||
name: string;
|
|
||||||
data: number[];
|
|
||||||
}
|
|
||||||
export interface BarChartData {
|
|
||||||
axisData: string[];
|
|
||||||
series: BarChartSeries[];
|
|
||||||
}
|
|
||||||
export interface PieChartData {
|
|
||||||
name: string;
|
|
||||||
value: number
|
|
||||||
}
|
|
||||||
export interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
export interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
||||||
type: 'chart';
|
type: 'chart';
|
||||||
chartType: string;
|
chartType: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
data: PieChartData[] | BarChartData;
|
data: Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableCell {
|
export interface TableCell {
|
||||||
|
@ -111,19 +87,13 @@ export interface PPTTableElement extends PPTElementBaseProps, PPTElementSizeProp
|
||||||
colSizes: number[];
|
colSizes: number[];
|
||||||
data: TableCell[][];
|
data: TableCell[][];
|
||||||
}
|
}
|
||||||
export interface PPTIframeElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
|
||||||
type: 'iframe';
|
|
||||||
src: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PPTElement = PPTTextElement |
|
export type PPTElement = PPTTextElement |
|
||||||
PPTImageElement |
|
PPTImageElement |
|
||||||
PPTShapeElement |
|
PPTShapeElement |
|
||||||
PPTIconElement |
|
|
||||||
PPTLineElement |
|
PPTLineElement |
|
||||||
PPTChartElement |
|
PPTChartElement |
|
||||||
PPTTableElement |
|
PPTTableElement
|
||||||
PPTIframeElement
|
|
||||||
|
|
||||||
export interface PPTAnimation {
|
export interface PPTAnimation {
|
||||||
elId: string;
|
elId: string;
|
||||||
|
|
|
@ -169,6 +169,17 @@ export const copyText = (text: string) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取剪贴板
|
||||||
|
export const readClipboard = () => {
|
||||||
|
if(navigator.clipboard) {
|
||||||
|
navigator.clipboard.readText().then(text => {
|
||||||
|
if(!text) return { err: '剪贴板为空或者不包含文本' }
|
||||||
|
return { text }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return { err: '浏览器不支持或禁止访问剪贴板' }
|
||||||
|
}
|
||||||
|
|
||||||
// 加密函数
|
// 加密函数
|
||||||
export const encrypt = (msg: string) => {
|
export const encrypt = (msg: string) => {
|
||||||
return CryptoJS.AES.encrypt(msg, CRYPTO_KEY).toString()
|
return CryptoJS.AES.encrypt(msg, CRYPTO_KEY).toString()
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
import { ElementAlignCommand, ElementAlignCommands } from '@/types/edit'
|
||||||
|
import { getElementListRange } from './elementRange'
|
||||||
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||||
|
|
||||||
|
// 将元素对齐到屏幕
|
||||||
|
export const alignElement = (elementList: PPTElement[], activeElementList: PPTElement[], activeElementIdList: string[], command: ElementAlignCommand) => {
|
||||||
|
const viewportWidth = VIEWPORT_SIZE
|
||||||
|
const viewportHeight = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
|
||||||
|
const { minX, maxX, minY, maxY } = getElementListRange(activeElementList)
|
||||||
|
|
||||||
|
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
for(const element of copyOfElementList) {
|
||||||
|
if(!activeElementIdList.includes(element.elId)) continue
|
||||||
|
|
||||||
|
if(command === ElementAlignCommands.TOP) {
|
||||||
|
const offsetY = minY - 0
|
||||||
|
element.top = element.top - offsetY
|
||||||
|
}
|
||||||
|
else if(command === ElementAlignCommands.VERTICAL) {
|
||||||
|
const offsetY = minY + (maxY - minY) / 2 - viewportHeight / 2
|
||||||
|
element.top = element.top - offsetY
|
||||||
|
}
|
||||||
|
else if(command === ElementAlignCommands.BOTTOM) {
|
||||||
|
const offsetY = maxY - viewportHeight
|
||||||
|
element.top = element.top - offsetY
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(command === ElementAlignCommands.LEFT) {
|
||||||
|
const offsetX = minX - 0
|
||||||
|
element.left = element.left - offsetX
|
||||||
|
}
|
||||||
|
else if(command === ElementAlignCommands.HORIZONTAL) {
|
||||||
|
const offsetX = minX + (maxX - minX) / 2 - viewportWidth / 2
|
||||||
|
element.left = element.left - offsetX
|
||||||
|
}
|
||||||
|
else if(command === ElementAlignCommands.RIGHT) {
|
||||||
|
const offsetX = maxX - viewportWidth
|
||||||
|
element.left = element.left - offsetX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { createRandomCode } from '@/utils/index'
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
|
||||||
|
// 组合元素(为当前所有激活元素添加一个相同的groupId)
|
||||||
|
export const combineElements = (elementList: PPTElement[], activeElementList: PPTElement[], activeElementIdList: string[]) => {
|
||||||
|
if(!activeElementList.length) return null
|
||||||
|
|
||||||
|
let copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
const groupId = createRandomCode()
|
||||||
|
|
||||||
|
const combineElementList: PPTElement[] = []
|
||||||
|
for(const element of copyOfElementList) {
|
||||||
|
if(activeElementIdList.includes(element.elId)) {
|
||||||
|
element.groupId = groupId
|
||||||
|
combineElementList.push(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意,组合元素的层级应该是连续的,所以需要获取该组元素中最顶层的元素,将组内其他成员从原位置移动到最顶层的元素的下面
|
||||||
|
const combineElementMaxIndex = copyOfElementList.findIndex(_element => _element.elId === combineElementList[combineElementList.length - 1].elId)
|
||||||
|
const combineElementIdList = combineElementList.map(_element => _element.elId)
|
||||||
|
copyOfElementList = copyOfElementList.filter(_element => !combineElementIdList.includes(_element.elId))
|
||||||
|
|
||||||
|
const insertIndex = combineElementMaxIndex - combineElementList.length + 1
|
||||||
|
copyOfElementList.splice(insertIndex, 0, ...combineElementList)
|
||||||
|
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消组合元素(移除所有被激活元素的groupId)
|
||||||
|
export const uncombineElements = (elementList: PPTElement[], activeElementList: PPTElement[], activeElementIdList: string[]) => {
|
||||||
|
if(!activeElementList.length) return null
|
||||||
|
const hasElementInGroup = activeElementList.some(item => item.groupId)
|
||||||
|
if(!hasElementInGroup) return null
|
||||||
|
|
||||||
|
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
for(const element of copyOfElementList) {
|
||||||
|
if(activeElementIdList.includes(element.elId) && element.groupId) delete element.groupId
|
||||||
|
}
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
import { ElementLockCommand, ElementLockCommands } from '@/types/edit'
|
||||||
|
|
||||||
|
const lock = (copyOfElementList: PPTElement[], handleElement: PPTElement, activeElementIdList: string[]) => {
|
||||||
|
for(const element of copyOfElementList) {
|
||||||
|
if(activeElementIdList.includes(handleElement.elId)) element.isLock = true
|
||||||
|
}
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlock = (copyOfElementList: PPTElement[], handleElement: PPTElement) => {
|
||||||
|
if(handleElement.groupId) {
|
||||||
|
for(const element of copyOfElementList) {
|
||||||
|
if(element.groupId === handleElement.groupId) element.isLock = false
|
||||||
|
}
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const element of copyOfElementList) {
|
||||||
|
if(element.elId === handleElement.elId) {
|
||||||
|
element.isLock = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 锁定&解锁 元素
|
||||||
|
export const lockElement = (elementList: PPTElement[], handleElement: PPTElement, activeElementIdList: string[], command: ElementLockCommand) => {
|
||||||
|
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
|
||||||
|
if(command === ElementLockCommands.LOCK) return lock(copyOfElementList, handleElement, activeElementIdList)
|
||||||
|
return unlock(copyOfElementList, handleElement)
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
import { ElementOrderCommand, ElementOrderCommands } from '@/types/edit'
|
||||||
|
|
||||||
|
// 获取组合元素层级范围(组合成员中的最大层级和最小层级)
|
||||||
|
const getCombineElementIndexRange = (elementList: PPTElement[], combineElementList: PPTElement[]) => {
|
||||||
|
const minIndex = elementList.findIndex(_element => _element.elId === combineElementList[0].elId)
|
||||||
|
const maxIndex = elementList.findIndex(_element => _element.elId === combineElementList[combineElementList.length - 1].elId)
|
||||||
|
return { minIndex, maxIndex }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上移一层,返回移动后新的元素列表(下移一层逻辑类似)
|
||||||
|
const moveUpElement = (elementList: PPTElement[], element: PPTElement) => {
|
||||||
|
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
|
||||||
|
// 被操作的元素是组合元素成员
|
||||||
|
if(element.groupId) {
|
||||||
|
|
||||||
|
// 获取该组合元素全部成员,以及组合元素层级范围
|
||||||
|
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
|
||||||
|
const { minIndex, maxIndex } = getCombineElementIndexRange(elementList, combineElementList)
|
||||||
|
|
||||||
|
// 无法移动(已经处在顶层)
|
||||||
|
if(maxIndex === elementList.length - 1) return null
|
||||||
|
|
||||||
|
// 该组合元素上一层的元素,以下简称为【元素next】
|
||||||
|
const nextElement = copyOfElementList[maxIndex + 1]
|
||||||
|
|
||||||
|
// 从元素列表中移除该组合元素全部成员
|
||||||
|
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
|
||||||
|
|
||||||
|
// 如果【元素next】也是组合元素成员(另一个组合,不是上面被移除的那一组,以下简称为【组合next】)
|
||||||
|
// 需要获取【组合next】全部成员的长度,将上面移除的组合元素全部成员添加到【组合next】全部成员的上方
|
||||||
|
if(nextElement.groupId) {
|
||||||
|
const nextCombineElementList = copyOfElementList.filter(_element => _element.groupId === nextElement.groupId)
|
||||||
|
copyOfElementList.splice(minIndex + nextCombineElementList.length, 0, ...movedElementList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果【元素next】是单独的元素(非组合成员),将上面移除的组合元素全部成员添加到【元素next】上方
|
||||||
|
else copyOfElementList.splice(minIndex + 1, 0, ...movedElementList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 被操作的元素是单独的元素(非组合成员)
|
||||||
|
else {
|
||||||
|
|
||||||
|
// 元素在元素列表中的层级
|
||||||
|
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||||
|
|
||||||
|
// 无法移动(已经处在顶层)
|
||||||
|
if(elementIndex === elementList.length - 1) return null
|
||||||
|
|
||||||
|
// 上一层的元素,以下简称为【元素next】
|
||||||
|
const nextElement = copyOfElementList[elementIndex + 1]
|
||||||
|
|
||||||
|
// 从元素列表中移除被操作的元素
|
||||||
|
const movedElement = copyOfElementList.splice(elementIndex, 1)[0]
|
||||||
|
|
||||||
|
// 如果【元素next】是组合元素成员
|
||||||
|
// 需要获取该组合全部成员的长度,将上面移除的元素添加到该组合全部成员的上方
|
||||||
|
if(nextElement.groupId) {
|
||||||
|
const combineElementList = copyOfElementList.filter(_element => _element.groupId === nextElement.groupId)
|
||||||
|
copyOfElementList.splice(elementIndex + combineElementList.length, 0, movedElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果【元素next】是单独的元素(非组合成员),将上面移除的元素添加到【元素next】上方
|
||||||
|
else copyOfElementList.splice(elementIndex + 1, 0, movedElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下移一层
|
||||||
|
const moveDownElement = (elementList: PPTElement[], element: PPTElement) => {
|
||||||
|
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
|
||||||
|
if(element.groupId) {
|
||||||
|
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
|
||||||
|
const { minIndex } = getCombineElementIndexRange(elementList, combineElementList)
|
||||||
|
if(minIndex === 0) return null
|
||||||
|
const prevElement = copyOfElementList[minIndex - 1]
|
||||||
|
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
|
||||||
|
if(prevElement.groupId) {
|
||||||
|
const prevCombineElementList = copyOfElementList.filter(_element => _element.groupId === prevElement.groupId)
|
||||||
|
copyOfElementList.splice(minIndex - prevCombineElementList.length, 0, ...movedElementList)
|
||||||
|
}
|
||||||
|
else copyOfElementList.splice(minIndex - 1, 0, ...movedElementList)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||||
|
if(elementIndex === 0) return null
|
||||||
|
const prevElement = copyOfElementList[elementIndex - 1]
|
||||||
|
const movedElement = copyOfElementList.splice(elementIndex, 1)[0]
|
||||||
|
if(prevElement.groupId) {
|
||||||
|
const combineElementList = copyOfElementList.filter(_element => _element.groupId === prevElement.groupId)
|
||||||
|
copyOfElementList.splice(elementIndex - combineElementList.length, 0, movedElement)
|
||||||
|
}
|
||||||
|
else copyOfElementList.splice(elementIndex - 1, 0, movedElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 置顶层,返回移动后新的元素列表(置底层逻辑类似)
|
||||||
|
const moveTopElement = (elementList: PPTElement[], element: PPTElement) => {
|
||||||
|
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
|
||||||
|
// 被操作的元素是组合元素成员
|
||||||
|
if(element.groupId) {
|
||||||
|
|
||||||
|
// 获取该组合元素全部成员,以及组合元素层级范围
|
||||||
|
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
|
||||||
|
const { minIndex, maxIndex } = getCombineElementIndexRange(elementList, combineElementList)
|
||||||
|
|
||||||
|
// 无法移动(已经处在顶层)
|
||||||
|
if(maxIndex === elementList.length - 1) return null
|
||||||
|
|
||||||
|
// 从元素列表中移除该组合元素全部成员,然后添加到元素列表最上方
|
||||||
|
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
|
||||||
|
copyOfElementList.push(...movedElementList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 被操作的元素是单独的元素(非组合成员)
|
||||||
|
else {
|
||||||
|
|
||||||
|
// 元素在元素列表中的层级
|
||||||
|
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||||
|
|
||||||
|
// 无法移动(已经处在顶层)
|
||||||
|
if(elementIndex === elementList.length - 1) return null
|
||||||
|
|
||||||
|
// 从元素列表中移除该元素,然后添加到元素列表最上方
|
||||||
|
copyOfElementList.splice(elementIndex, 1)
|
||||||
|
copyOfElementList.push(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 置底层
|
||||||
|
const moveBottomElement = (elementList: PPTElement[], element: PPTElement) => {
|
||||||
|
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
|
||||||
|
|
||||||
|
if(element.groupId) {
|
||||||
|
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
|
||||||
|
const { minIndex } = getCombineElementIndexRange(elementList, combineElementList)
|
||||||
|
if(minIndex === 0) return null
|
||||||
|
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
|
||||||
|
copyOfElementList.unshift(...movedElementList)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||||
|
if(elementIndex === 0) return null
|
||||||
|
copyOfElementList.splice(elementIndex, 1)
|
||||||
|
copyOfElementList.unshift(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyOfElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setElementOrder = (elementList: PPTElement[], element: PPTElement, command: ElementOrderCommand) => {
|
||||||
|
let newElementList = null
|
||||||
|
|
||||||
|
if(command === ElementOrderCommands.UP) newElementList = moveUpElement(elementList, element)
|
||||||
|
else if(command === ElementOrderCommands.DOWN) newElementList = moveDownElement(elementList, element)
|
||||||
|
else if(command === ElementOrderCommands.TOP) newElementList = moveTopElement(elementList, element)
|
||||||
|
else if(command === ElementOrderCommands.BOTTOM) newElementList = moveBottomElement(elementList, element)
|
||||||
|
|
||||||
|
return newElementList
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement, PPTIconElement } from '@/types/slides'
|
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement } from '@/types/slides'
|
||||||
|
|
||||||
// 获取矩形旋转后在画布中的位置范围
|
// 获取矩形旋转后在画布中的位置范围
|
||||||
export const getRectRotatedRange = (element: PPTTextElement | PPTImageElement | PPTShapeElement | PPTIconElement) => {
|
export const getRectRotatedRange = (element: PPTTextElement | PPTImageElement | PPTShapeElement) => {
|
||||||
const { left, top, width, height, rotate = 0 } = element
|
const { left, top, width, height, rotate = 0 } = element
|
||||||
|
|
||||||
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2
|
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { PPTTextElement, PPTImageElement, PPTShapeElement, PPTIconElement } from '@/types/slides'
|
import { PPTTextElement, PPTImageElement, PPTShapeElement } from '@/types/slides'
|
||||||
import { OPERATE_KEYS } from '@/configs/element'
|
import { OPERATE_KEYS } from '@/configs/element'
|
||||||
|
|
||||||
// 给定一个坐标,计算该坐标到(0, 0)点连线的弧度值
|
// 给定一个坐标,计算该坐标到(0, 0)点连线的弧度值
|
||||||
|
@ -11,7 +11,7 @@ export const getAngleFromCoordinate = (x: number, y: number) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算元素被旋转一定角度后,八个操作点的新坐标
|
// 计算元素被旋转一定角度后,八个操作点的新坐标
|
||||||
export const getRotateElementPoints = (element: PPTTextElement | PPTImageElement | PPTShapeElement | PPTIconElement, angle: number) => {
|
export const getRotateElementPoints = (element: PPTTextElement | PPTImageElement | PPTShapeElement, angle: number) => {
|
||||||
const { left, top, width, height } = element
|
const { left, top, width, height } = element
|
||||||
|
|
||||||
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2
|
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2
|
||||||
|
|
|
@ -45,9 +45,6 @@ export default defineComponent({
|
||||||
const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
|
const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
|
||||||
const disableHotkeys = computed(() => store.state.disableHotkeys)
|
const disableHotkeys = computed(() => store.state.disableHotkeys)
|
||||||
|
|
||||||
const save = () => {
|
|
||||||
message.success('save')
|
|
||||||
}
|
|
||||||
const copy = () => {
|
const copy = () => {
|
||||||
message.success('copy')
|
message.success('copy')
|
||||||
}
|
}
|
||||||
|
@ -90,10 +87,6 @@ export default defineComponent({
|
||||||
|
|
||||||
if(!editorAreaFocus.value && !thumbnailsFocus.value) return
|
if(!editorAreaFocus.value && !thumbnailsFocus.value) return
|
||||||
|
|
||||||
if(ctrlKey && keyCode === KEYCODE.S) {
|
|
||||||
e.preventDefault()
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
if(ctrlKey && keyCode === KEYCODE.C) {
|
if(ctrlKey && keyCode === KEYCODE.C) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
copy()
|
copy()
|
||||||
|
|
|
@ -23,6 +23,16 @@
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, defineComponent, PropType } from 'vue'
|
||||||
import { PPTElement } from '@/types/slides'
|
import { PPTElement } from '@/types/slides'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ElementOrderCommand,
|
||||||
|
ElementOrderCommands,
|
||||||
|
ElementAlignCommand,
|
||||||
|
ElementAlignCommands,
|
||||||
|
ElementScaleHandler,
|
||||||
|
ElementLockCommand,
|
||||||
|
ElementLockCommands,
|
||||||
|
} from '@/types/edit'
|
||||||
|
|
||||||
import ImageElement from './ImageElement.index.vue'
|
import ImageElement from './ImageElement.index.vue'
|
||||||
import TextElement from './TextElement.index.vue'
|
import TextElement from './TextElement.index.vue'
|
||||||
|
|
||||||
|
@ -62,11 +72,11 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
scaleElement: {
|
scaleElement: {
|
||||||
type: Function as PropType<(e: MouseEvent, element: PPTElement, direction: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: ElementScaleHandler) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
updateZIndex: {
|
orderElement: {
|
||||||
type: Function as PropType<(element: PPTElement, operation: 'up' | 'down' | 'top' | 'bottom') => void>,
|
type: Function as PropType<(element: PPTElement, command: ElementOrderCommand) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
combineElements: {
|
combineElements: {
|
||||||
|
@ -78,7 +88,7 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
alignElement: {
|
alignElement: {
|
||||||
type: Function as PropType<(direction: 'top' | 'verticalCenter' | 'bottom' | 'left' | 'horizontalCenter' | 'right') => void>,
|
type: Function as PropType<(command: ElementAlignCommand) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
deleteElement: {
|
deleteElement: {
|
||||||
|
@ -86,7 +96,7 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
lockElement: {
|
lockElement: {
|
||||||
type: Function as PropType<(element: PPTElement, handle: 'lock' | 'unlock') => void>,
|
type: Function as PropType<(element: PPTElement, command: ElementLockCommand) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
copyElement: {
|
copyElement: {
|
||||||
|
@ -112,7 +122,7 @@ export default defineComponent({
|
||||||
return [{
|
return [{
|
||||||
text: '解锁',
|
text: '解锁',
|
||||||
icon: 'icon-unlock',
|
icon: 'icon-unlock',
|
||||||
action: () => props.lockElement(props.elementInfo, 'unlock'),
|
action: () => props.lockElement(props.elementInfo, ElementLockCommands.UNLOCK),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,29 +145,29 @@ export default defineComponent({
|
||||||
icon: 'icon-top-layer',
|
icon: 'icon-top-layer',
|
||||||
disable: props.isMultiSelect && !props.elementInfo.groupId,
|
disable: props.isMultiSelect && !props.elementInfo.groupId,
|
||||||
children: [
|
children: [
|
||||||
{ text: '置顶层', action: () => props.updateZIndex(props.elementInfo, 'top') },
|
{ text: '置顶层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.TOP) },
|
||||||
{ text: '置底层', action: () => props.updateZIndex(props.elementInfo, 'bottom') },
|
{ text: '置底层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ text: '上移一层', action: () => props.updateZIndex(props.elementInfo, 'up') },
|
{ text: '上移一层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.UP) },
|
||||||
{ text: '下移一层', action: () => props.updateZIndex(props.elementInfo, 'down') },
|
{ text: '下移一层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '水平对齐',
|
text: '水平对齐',
|
||||||
icon: 'icon-align-left',
|
icon: 'icon-align-left',
|
||||||
children: [
|
children: [
|
||||||
{ text: '水平居中', action: () => props.alignElement('horizontalCenter') },
|
{ text: '水平居中', action: () => props.alignElement(ElementAlignCommands.HORIZONTAL) },
|
||||||
{ text: '左对齐', action: () => props.alignElement('left') },
|
{ text: '左对齐', action: () => props.alignElement(ElementAlignCommands.LEFT) },
|
||||||
{ text: '右对齐', action: () => props.alignElement('right') },
|
{ text: '右对齐', action: () => props.alignElement(ElementAlignCommands.RIGHT) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '垂直对齐',
|
text: '垂直对齐',
|
||||||
icon: 'icon-align-bottom',
|
icon: 'icon-align-bottom',
|
||||||
children: [
|
children: [
|
||||||
{ text: '垂直居中', action: () => props.alignElement('verticalCenter') },
|
{ text: '垂直居中', action: () => props.alignElement(ElementAlignCommands.VERTICAL) },
|
||||||
{ text: '上对齐', action: () => props.alignElement('top') },
|
{ text: '上对齐', action: () => props.alignElement(ElementAlignCommands.TOP) },
|
||||||
{ text: '下对齐', action: () => props.alignElement('bottom') },
|
{ text: '下对齐', action: () => props.alignElement(ElementAlignCommands.BOTTOM) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
|
@ -172,7 +182,7 @@ export default defineComponent({
|
||||||
text: '锁定',
|
text: '锁定',
|
||||||
subText: 'Ctrl + L',
|
subText: 'Ctrl + L',
|
||||||
icon: 'icon-lock',
|
icon: 'icon-lock',
|
||||||
action: () => props.lockElement(props.elementInfo, 'lock'),
|
action: () => props.lockElement(props.elementInfo, ElementLockCommands.LOCK),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '删除',
|
text: '删除',
|
||||||
|
|
Loading…
Reference in New Issue