pipipi-pikachu %!s(int64=5) %!d(string=hai) anos
pai
achega
f575fa56b3

+ 0 - 160
src/configs/animation.ts

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

+ 0 - 15
src/hooks/useAddHistorySnapshot.ts

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

+ 5 - 0
src/hooks/useCombineElement.ts

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

+ 4 - 0
src/hooks/useCreateElement.ts

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

+ 5 - 0
src/hooks/useDeleteElement.ts

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

+ 6 - 0
src/hooks/useRedoAndUndo.ts

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

+ 5 - 0
src/hooks/useLockElement.ts

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

+ 4 - 0
src/hooks/useMoveElement.ts

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

+ 4 - 0
src/hooks/useOrderElement.ts

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

+ 5 - 0
src/hooks/usePasteTextClipboardData.ts

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

+ 5 - 0
src/hooks/useSlideHandler.ts

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

+ 14 - 6
src/store/actions.ts

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

+ 4 - 0
src/views/Editor/Canvas/hooks/useDragElement.ts

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

+ 5 - 1
src/views/Editor/Canvas/hooks/useRotateElement.ts

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

+ 7 - 2
src/views/Editor/Canvas/hooks/useScaleElement.ts

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

+ 14 - 2
src/views/Editor/CanvasTool/index.vue

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

+ 2 - 9
src/views/Editor/useHotkey.ts

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