This commit is contained in:
pipipi-pikachu 2020-12-23 21:31:55 +08:00
parent 2b49692319
commit f575fa56b3
17 changed files with 89 additions and 195 deletions

View File

@ -1,160 +0,0 @@
export const ANIMATIONS_TYPES = ['弹跳', '淡入', '翻转', '旋转', '滑入', '缩放']
export const ANIMATIONS = [
{
key: 'bounceIn',
type: '弹跳',
name: '弹跳',
icon: 'icon-anime-bounce'
},
{
key: 'bounceInDown',
type: '弹跳',
name: '向下弹跳',
icon: 'icon-anime-bounce-down',
},
{
key: 'bounceInLeft',
type: '弹跳',
name: '从左弹跳',
icon: 'icon-anime-bounce-left',
},
{
key: 'bounceInRight',
type: '弹跳',
name: '从右弹跳',
icon: 'icon-anime-bounce-right',
},
{
key: 'bounceInUp',
type: '弹跳',
name: '向上弹跳',
icon: 'icon-anime-bounce-up',
},
{
key: 'fadeIn',
type: '淡入',
name: '淡入',
icon: 'icon-anime-fade',
},
{
key: 'fadeInDown',
type: '淡入',
name: '向下淡入',
icon: 'icon-anime-fade-down',
},
{
key: 'fadeInLeft',
type: '淡入',
name: '从左淡入',
icon: 'icon-anime-fade-left',
},
{
key: 'fadeInRight',
type: '淡入',
name: '从右淡入',
icon: 'icon-anime-fade-right',
},
{
key: 'fadeInUp',
type: '淡入',
name: '向上淡入',
icon: 'icon-anime-fade-up',
},
{
key: 'flipInX',
type: '翻转',
name: '水平翻转',
icon: 'icon-anime-flip-x',
},
{
key: 'flipInY',
type: '翻转',
name: '垂直翻转',
icon: 'icon-anime-flip-y',
},
{
key: 'rotateIn',
type: '旋转',
name: '旋转',
icon: 'icon-anime-rotate',
},
{
key: 'rotateInDownLeft',
type: '旋转',
name: '从左下旋转',
icon: 'icon-anime-rotate-up-right',
},
{
key: 'rotateInDownRight',
type: '旋转',
name: '从右下旋转',
icon: 'icon-anime-rotate-up-left',
},
{
key: 'rotateInUpLeft',
type: '旋转',
name: '从左上旋转',
icon: 'icon-anime-rotate-down-right',
},
{
key: 'rotateInUpRight',
type: '旋转',
name: '从右上旋转',
icon: 'icon-anime-rotate-down-left',
},
{
key: 'slideInDown',
type: '滑入',
name: '向下滑入',
icon: 'icon-anime-slide-down',
},
{
key: 'slideInLeft',
type: '滑入',
name: '从左滑入',
icon: 'icon-anime-slide-left',
},
{
key: 'slideInRight',
type: '滑入',
name: '从右滑入',
icon: 'icon-anime-slide-right',
},
{
key: 'slideInUp',
type: '滑入',
name: '向上滑入',
icon: 'icon-anime-slide-up',
},
{
key: 'zoomIn',
type: '缩放',
name: '放大',
icon: 'icon-anime-zoom',
},
{
key: 'zoomInDown',
type: '缩放',
name: '向下放大',
icon: 'icon-anime-zoom-down',
},
{
key: 'zoomInLeft',
type: '缩放',
name: '从左放大',
icon: 'icon-anime-zoom-left',
},
{
key: 'zoomInRight',
type: '缩放',
name: '从右放大',
icon: 'icon-anime-zoom-right',
},
{
key: 'zoomInUp',
type: '缩放',
name: '向上放大',
icon: 'icon-anime-zoom-up',
},
]

View File

@ -1,15 +0,0 @@
import { useStore } from 'vuex'
import debounce from 'lodash/debounce'
import { State, ActionTypes } from '@/store'
export default () => {
const store = useStore<State>()
const addHistorySnapshot = debounce(function() {
store.dispatch(ActionTypes.ADD_SNAPSHOT)
}, 300, { trailing: true })
return {
addHistorySnapshot,
}
}

View File

@ -3,6 +3,7 @@ import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { PPTElement, Slide } from '@/types/slides'
import { createRandomCode } from '@/utils/common'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => {
const store = useStore<State>()
@ -10,6 +11,8 @@ export default () => {
const activeElementList: Ref<PPTElement[]> = computed(() => store.getters.activeElementList)
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const { addHistorySnapshot } = useHistorySnapshot()
// 组合元素为当前所有激活元素添加一个相同的groupId
const combineElements = () => {
if(!activeElementList.value.length) return
@ -34,6 +37,7 @@ export default () => {
newElementList.splice(insertIndex, 0, ...combineElementList)
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot()
}
// 取消组合元素移除所有被激活元素的groupId
@ -47,6 +51,7 @@ export default () => {
if(activeElementIdList.value.includes(element.elId) && element.groupId) delete element.groupId
}
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot()
}
return {

View File

@ -12,6 +12,7 @@ import {
DEFAULT_CHART,
DEFAULT_TABLE,
} from '@/configs/element'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
interface CommonElementPosition {
top: number;
@ -30,9 +31,12 @@ interface LineElementPosition {
export default () => {
const store = useStore()
const { addHistorySnapshot } = useHistorySnapshot()
const createElement = (element: PPTElement) => {
store.commit(MutationTypes.ADD_ELEMENT, element)
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [element.elId])
addHistorySnapshot()
}
const createImageElement = (imgUrl: string) => {

View File

@ -2,23 +2,28 @@ import { Ref, computed } from 'vue'
import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { Slide } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => {
const store = useStore<State>()
const activeElementIdList = computed(() => store.state.activeElementIdList)
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const { addHistorySnapshot } = useHistorySnapshot()
const deleteElement = () => {
if(!activeElementIdList.value.length) return
const newElementList = currentSlide.value.elements.filter(el => !activeElementIdList.value.includes(el.elId))
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot()
}
const deleteAllElements = () => {
if(!currentSlide.value.elements.length) return
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
store.commit(MutationTypes.UPDATE_SLIDE, { elements: [] })
addHistorySnapshot()
}
return {

View File

@ -1,10 +1,15 @@
import { useStore } from 'vuex'
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
import { State, ActionTypes } from '@/store'
export default () => {
const store = useStore<State>()
const addHistorySnapshot = debounce(function() {
store.dispatch(ActionTypes.ADD_SNAPSHOT)
}, 300, { trailing: true })
const redo = throttle(function() {
store.dispatch(ActionTypes.RE_DO)
}, 100, { leading: true, trailing: false })
@ -14,6 +19,7 @@ export default () => {
}, 100, { leading: true, trailing: false })
return {
addHistorySnapshot,
redo,
undo,
}

View File

@ -2,12 +2,15 @@ import { useStore } from 'vuex'
import { Ref, computed } from 'vue'
import { State, MutationTypes } from '@/store'
import { PPTElement, Slide } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => {
const store = useStore<State>()
const activeElementIdList = computed(() => store.state.activeElementIdList)
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const { addHistorySnapshot } = useHistorySnapshot()
const lockElement = () => {
const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
@ -15,6 +18,7 @@ export default () => {
if(activeElementIdList.value.includes(element.elId)) element.isLock = true
}
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot()
}
const unlockElement = (handleElement: PPTElement) => {
@ -34,6 +38,7 @@ export default () => {
}
}
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot()
}
return {

View File

@ -3,12 +3,15 @@ import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { Slide } from '@/types/slides'
import { KEYS } from '@/configs/hotkey'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => {
const store = useStore<State>()
const activeElementIdList = computed(() => store.state.activeElementIdList)
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const { addHistorySnapshot } = useHistorySnapshot()
const moveElement = (command: string) => {
const newElementList = currentSlide.value.elements.map(el => {
if(activeElementIdList.value.includes(el.elId)) {
@ -33,6 +36,7 @@ export default () => {
return el
})
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot()
}
return {

View File

@ -3,11 +3,14 @@ import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { PPTElement, Slide } from '@/types/slides'
import { ElementOrderCommand, ElementOrderCommands } from '@/types/edit'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => {
const store = useStore<State>()
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const { addHistorySnapshot } = useHistorySnapshot()
// 获取组合元素层级范围(组合成员中的最大层级和最小层级)
const getCombineElementIndexRange = (elementList: PPTElement[], combineElementList: PPTElement[]) => {
const minIndex = elementList.findIndex(_element => _element.elId === combineElementList[0].elId)
@ -174,6 +177,7 @@ export default () => {
else if(command === ElementOrderCommands.BOTTOM) newElementList = moveBottomElement(currentSlide.value.elements, element)
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot()
}
return {

View File

@ -4,6 +4,7 @@ import { MutationTypes, State } from '@/store'
import { decrypt } from '@/utils/crypto'
import { PPTElement, Slide } from '@/types/slides'
import { createRandomCode } from '@/utils/common'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
interface PasteTextClipboardDataOptions {
onlySlide?: boolean;
@ -14,6 +15,8 @@ export default () => {
const store = useStore<State>()
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const { addHistorySnapshot } = useHistorySnapshot()
const pasteElement = (elements: PPTElement[]) => {
const groupIdMap = {}
const elIdMap = {}
@ -40,10 +43,12 @@ export default () => {
}
store.commit(MutationTypes.ADD_ELEMENT, elements)
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, Object.values(elIdMap))
addHistorySnapshot()
}
const pasteSlide = (slide: Slide) => {
store.commit(MutationTypes.ADD_SLIDE, slide)
addHistorySnapshot()
}
const pasteText = (text: string) => {

View File

@ -8,6 +8,7 @@ import { encrypt } from '@/utils/crypto'
import { KEYS } from '@/configs/hotkey'
import { message } from 'ant-design-vue'
import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => {
const store = useStore<State>()
@ -16,6 +17,7 @@ export default () => {
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const { pasteTextClipboardData } = usePasteTextClipboardData()
const { addHistorySnapshot } = useHistorySnapshot()
const updateSlideIndex = (command: string) => {
let targetIndex = 0
@ -51,14 +53,17 @@ export default () => {
elements: [],
}
store.commit(MutationTypes.ADD_SLIDE, emptySlide)
addHistorySnapshot()
}
const copyAndPasteSlide = () => {
store.commit(MutationTypes.ADD_SLIDE, currentSlide.value)
addHistorySnapshot()
}
const deleteSlide = () => {
store.commit(MutationTypes.DELETE_SLIDE, currentSlide.value.id)
addHistorySnapshot()
}
const cutSlide = () => {

View File

@ -5,14 +5,22 @@ import { ActionTypes, MutationTypes } from './constants'
import db, { Snapshot } from '@/utils/database'
export const actions: ActionTree<State, State> = {
async [ActionTypes.INIT_SNAPSHOT_DATABASE]({ commit }) {
async [ActionTypes.INIT_SNAPSHOT_DATABASE]({ commit, state }) {
const snapshots: Snapshot[] = await db.snapshots.orderBy('id').toArray()
const snapshot = snapshots.slice(-1)[0]
const lastSnapshot = snapshots.slice(-1)[0]
if(snapshot) {
if(lastSnapshot) {
db.snapshots.clear()
commit(MutationTypes.SET_SLIDES, snapshot.slides)
// commit(MutationTypes.SET_SLIDES, lastSnapshot.slides)
}
const newFirstSnapshot = {
index: state.slideIndex,
slides: state.slides,
}
await db.snapshots.add(newFirstSnapshot)
commit(MutationTypes.SET_SNAPSHOT_CURSOR, 0)
commit(MutationTypes.SET_SNAPSHOT_LENGTH, 1)
},
async [ActionTypes.ADD_SNAPSHOT]({ state, commit }) {
@ -44,7 +52,7 @@ export const actions: ActionTree<State, State> = {
},
async [ActionTypes.UN_DO]({ state, commit }) {
if(state.snapshotCursor > 0) return
if(state.snapshotCursor <= 0) return
const snapshotCursor = state.snapshotCursor - 1
const snapshots: Snapshot[] = await db.snapshots.orderBy('id').toArray()
@ -58,7 +66,7 @@ export const actions: ActionTree<State, State> = {
},
async [ActionTypes.RE_DO]({ state, commit }) {
if(state.snapshotCursor < state.snapshotLength - 1) return
if(state.snapshotCursor >= state.snapshotLength - 1) return
const snapshotCursor = state.snapshotCursor + 1
const snapshots: Snapshot[] = await db.snapshots.orderBy('id').toArray()

View File

@ -5,6 +5,7 @@ import { ElementTypes, PPTElement } from '@/types/slides'
import { AlignmentLineProps } from '@/types/edit'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
import { getRectRotatedRange, AlignLine, uniqAlignLines } from '@/utils/element'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default (
elementList: Ref<PPTElement[]>,
@ -15,6 +16,8 @@ export default (
const activeElementIdList = computed(() => store.state.activeElementIdList)
const canvasScale = computed(() => store.state.canvasScale)
const { addHistorySnapshot } = useHistorySnapshot()
const dragElement = (e: MouseEvent, element: PPTElement) => {
if(!activeElementIdList.value.includes(element.elId)) return
let isMouseDown = true
@ -305,6 +308,7 @@ export default (
if(startPageX === currentPageX && startPageY === currentPageY) return
store.commit(MutationTypes.UPDATE_SLIDE, { elements: elementList.value })
addHistorySnapshot()
}
}

View File

@ -2,11 +2,12 @@ import { Ref, computed } from 'vue'
import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
// 给定一个坐标,计算该坐标到(0, 0)点连线的弧度值
// 注意Math.atan2的一般用法是Math.atan2(y, x)返回的是原点(0,0)到(x,y)点的线段与X轴正方向之间的弧度值
// 这里将使用时将x与y的传入顺序交换了为的是获取原点(0,0)到(x,y)点的线段与Y轴正方向之间的弧度值
export const getAngleFromCoordinate = (x: number, y: number) => {
const getAngleFromCoordinate = (x: number, y: number) => {
const radian = Math.atan2(x, y)
const angle = 180 / Math.PI * radian
return angle
@ -16,6 +17,8 @@ export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | n
const store = useStore<State>()
const canvasScale = computed(() => store.state.canvasScale)
const { addHistorySnapshot } = useHistorySnapshot()
const rotateElement = (element: PPTTextElement | PPTImageElement | PPTShapeElement) => {
let isMouseDown = true
let angle = 0
@ -67,6 +70,7 @@ export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | n
if(elOriginRotate === angle) return
store.commit(MutationTypes.UPDATE_SLIDE, { elements: elementList.value })
addHistorySnapshot()
}
}

View File

@ -5,6 +5,7 @@ import { ElementTypes, PPTElement, PPTImageElement, PPTLineElement, PPTShapeElem
import { OperatePoints, ElementScaleHandler, AlignmentLineProps, MultiSelectRange } from '@/types/edit'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
import { AlignLine, uniqAlignLines } from '@/utils/element'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
// 计算元素被旋转一定角度后,八个操作点的新坐标
interface RotateElementData {
@ -13,7 +14,7 @@ interface RotateElementData {
width: number;
height: number;
}
export const getRotateElementPoints = (element: RotateElementData, angle: number) => {
const getRotateElementPoints = (element: RotateElementData, angle: number) => {
const { left, top, width, height } = element
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2
@ -67,7 +68,7 @@ export const getRotateElementPoints = (element: RotateElementData, angle: number
}
// 获取元素某个操作点对角线上另一端的操作点坐标(例如:左上 <-> 右下)
export const getOppositePoint = (direction: number, points: ReturnType<typeof getRotateElementPoints>): { left: number; top: number } => {
const getOppositePoint = (direction: number, points: ReturnType<typeof getRotateElementPoints>): { left: number; top: number } => {
const oppositeMap = {
[OperatePoints.RIGHT_BOTTOM]: points.leftTopPoint,
[OperatePoints.LEFT_BOTTOM]: points.rightTopPoint,
@ -91,6 +92,8 @@ export default (
const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
const canvasScale = computed(() => store.state.canvasScale)
const { addHistorySnapshot } = useHistorySnapshot()
const scaleElement = (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: ElementScaleHandler) => {
let isMouseDown = true
@ -383,6 +386,7 @@ export default (
if(startPageX === e.pageX && startPageY === e.pageY) return
store.commit(MutationTypes.UPDATE_SLIDE, { elements: elementList.value })
addHistorySnapshot()
}
}
@ -486,6 +490,7 @@ export default (
if(startPageX === e.pageX && startPageY === e.pageY) return
store.commit(MutationTypes.UPDATE_SLIDE, { elements: elementList.value })
addHistorySnapshot()
}
}

View File

@ -1,8 +1,8 @@
<template>
<div class="canvas-tool">
<div class="left-handler">
<IconFont class="handler-item" type="icon-undo" />
<IconFont class="handler-item" type="icon-redo" />
<IconFont class="handler-item" :class="{ 'disable': !canUndo }" type="icon-undo" @click="undo()" />
<IconFont class="handler-item" :class="{ 'disable': !canRedo }" type="icon-redo" @click="redo()" />
</div>
<div class="add-element-handler">
@ -28,6 +28,7 @@ import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, State } from '@/store'
import useScaleCanvas from '@/hooks/useScaleCanvas'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default defineComponent({
name: 'canvas-tool',
@ -35,10 +36,13 @@ export default defineComponent({
const store = useStore<State>()
const canvasScale = computed(() => store.state.canvasScale)
const showGridLines = computed(() => store.state.showGridLines)
const canUndo = computed(() => store.getters.canUndo)
const canRedo = computed(() => store.getters.canRedo)
const canvasScalePercentage = computed(() => parseInt(canvasScale.value * 100 + '') + '%')
const { scaleCanvas } = useScaleCanvas()
const { redo, undo } = useHistorySnapshot()
const toggleGridLines = () => {
store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
@ -48,6 +52,10 @@ export default defineComponent({
scaleCanvas,
canvasScalePercentage,
toggleGridLines,
canUndo,
canRedo,
redo,
undo,
}
},
})
@ -77,6 +85,10 @@ export default defineComponent({
.handler-item {
margin: 0 10px;
cursor: pointer;
&.disable {
opacity: .5;
}
}
.right-handler {
display: flex;

View File

@ -2,7 +2,6 @@ import { computed, onMounted, onUnmounted } from 'vue'
import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { KEYS } from '@/configs/hotkey'
import { message } from 'ant-design-vue'
import useSlideHandler from '@/hooks/useSlideHandler'
import useLockElement from '@/hooks/useLockElement'
@ -11,6 +10,7 @@ import useCombineElement from '@/hooks/useCombineElement'
import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
import useSelectAllElement from '@/hooks/useSelectAllElement'
import useMoveElement from '@/hooks/useMoveElement'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => {
const store = useStore<State>()
@ -37,6 +37,7 @@ export default () => {
const { copyElement, cutElement } = useCopyAndPasteElement()
const { selectAllElement } = useSelectAllElement()
const { moveElement } = useMoveElement()
const { redo, undo } = useHistorySnapshot()
const copy = () => {
if(disableHotkeys.value) return
@ -50,14 +51,6 @@ export default () => {
else if(activeElementIdList.value.length) cutElement()
}
const undo = () => {
message.success('undo')
}
const redo = () => {
message.success('redo')
}
const selectAll = () => {
if(!editorAreaFocus.value && disableHotkeys.value) return
selectAllElement()