Commit 8c099bb59d8a282bc1b16fca9fcce3f8bc542288
1 parent
ffebb06b
feat(editor): support undo-redo && first version
Showing
7 changed files
with
94 additions
and
2 deletions
front-end/h5/src/components/core/editor/canvas/edit.js
| ... | ... | @@ -45,6 +45,7 @@ export default { |
| 45 | 45 | 'setEditingElement', // -> this.foo() |
| 46 | 46 | 'setElementPosition', // -> this.foo() |
| 47 | 47 | 'setElementShape', // -> this.foo() |
| 48 | + 'recordElementRect', // -> this.foo() | |
| 48 | 49 | 'elementManager' |
| 49 | 50 | ]), |
| 50 | 51 | // TODO #!zh: 优化代码 |
| ... | ... | @@ -196,6 +197,12 @@ export default { |
| 196 | 197 | this.setElementPosition(pos) |
| 197 | 198 | }} |
| 198 | 199 | handleElementMoveProp={this.handleElementMove} |
| 200 | + handleElementMouseUpProp={() => { | |
| 201 | + this.recordElementRect() | |
| 202 | + }} | |
| 203 | + handlePointMouseUpProp={() => { | |
| 204 | + this.recordElementRect() | |
| 205 | + }} | |
| 199 | 206 | > |
| 200 | 207 | {h(element.name, data)} |
| 201 | 208 | </Shape> | ... | ... |
front-end/h5/src/components/core/editor/index.js
| 1 | 1 | import Vue from 'vue' |
| 2 | 2 | import { mapState, mapActions } from 'vuex' |
| 3 | 3 | // import Element from '../models/element' |
| 4 | +import undoRedoHistory from '../../../store/plugins/undo-redo/History' | |
| 4 | 5 | |
| 5 | 6 | import '../styles/index.scss' |
| 6 | 7 | |
| ... | ... | @@ -79,6 +80,12 @@ export default { |
| 79 | 80 | defaultSelectedKeys={['2']} |
| 80 | 81 | style={{ lineHeight: '64px', float: 'right', background: 'transparent' }} |
| 81 | 82 | > |
| 83 | + <a-menu-item key="4" class="transparent-bg"> | |
| 84 | + <a-button-group> | |
| 85 | + <a-button class="transparent-bg" style={{ color: 'white' }} type="dashed" size="small" onClick={() => undoRedoHistory.undo()}><i class={['shortcut-icon', 'fa', `fa-mail-reply`]} aria-hidden='true'/> 撤销</a-button> | |
| 86 | + <a-button class="transparent-bg" style={{ color: 'white' }} type="dashed" size="small" onClick={() => undoRedoHistory.redo()}><i class={['shortcut-icon', 'fa', `fa-mail-forward`]} aria-hidden='true'/> 重做</a-button> | |
| 87 | + </a-button-group> | |
| 88 | + </a-menu-item> | |
| 82 | 89 | <a-menu-item key="1" class="transparent-bg"><a-button type="primary" size="small">预览</a-button></a-menu-item> |
| 83 | 90 | <a-menu-item key="2" class="transparent-bg"><a-button size="small">保存</a-button></a-menu-item> |
| 84 | 91 | <a-menu-item key="3" class="transparent-bg"><a-button size="small">发布</a-button></a-menu-item> | ... | ... |
front-end/h5/src/components/core/support/shape.js
| ... | ... | @@ -13,7 +13,7 @@ const directionKey = { |
| 13 | 13 | const points = ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b'] |
| 14 | 14 | |
| 15 | 15 | export default { |
| 16 | - props: ['defaultPosition', 'active', 'handleMousedownProp', 'handleElementMoveProp', 'handlePointMoveProp'], | |
| 16 | + props: ['defaultPosition', 'active', 'handleMousedownProp', 'handleElementMoveProp', 'handlePointMoveProp', 'handleElementMouseUpProp', 'handlePointMouseUpProp'], | |
| 17 | 17 | computed: { |
| 18 | 18 | position () { |
| 19 | 19 | return { ...this.defaultPosition } | ... | ... |
front-end/h5/src/store/index.js
| 1 | 1 | import Vue from 'vue' |
| 2 | 2 | import Vuex from 'vuex' |
| 3 | +import undoRedoPlugin from './plugins/undo-redo/index' | |
| 3 | 4 | import editor from './modules/editor' |
| 4 | 5 | import user from './modules/user' |
| 5 | 6 | import visible from './modules/visible' |
| ... | ... | @@ -24,5 +25,6 @@ export default new Vuex.Store({ |
| 24 | 25 | visible, |
| 25 | 26 | loading, |
| 26 | 27 | element |
| 27 | - } | |
| 28 | + }, | |
| 29 | + plugins: [undoRedoPlugin] | |
| 28 | 30 | }) | ... | ... |
front-end/h5/src/store/modules/element.js
| ... | ... | @@ -22,6 +22,9 @@ const actions = { |
| 22 | 22 | setElementShape ({ commit }, payload) { |
| 23 | 23 | commit('setElementCommonStyle', payload) |
| 24 | 24 | }, |
| 25 | + recordElementRect ({ commit }, payload = {}) { | |
| 26 | + commit('recordRect', payload) | |
| 27 | + }, | |
| 25 | 28 | elementManager ({ commit }, payload) { |
| 26 | 29 | commit('elementManager', payload) |
| 27 | 30 | } |
| ... | ... | @@ -58,6 +61,9 @@ const mutations = { |
| 58 | 61 | break |
| 59 | 62 | default: |
| 60 | 63 | } |
| 64 | + }, | |
| 65 | + recordRect (state, { type, value }) { | |
| 66 | + | |
| 61 | 67 | } |
| 62 | 68 | } |
| 63 | 69 | ... | ... |
front-end/h5/src/store/plugins/undo-redo/History.js
0 → 100644
| 1 | +import { cloneDeep } from 'lodash' | |
| 2 | + | |
| 3 | +class UndoRedoHistory { | |
| 4 | + store; | |
| 5 | + history = []; | |
| 6 | + currentIndex = -1; | |
| 7 | + | |
| 8 | + get canUndo () { | |
| 9 | + return this.currentIndex > 0 | |
| 10 | + } | |
| 11 | + | |
| 12 | + get canRedo () { | |
| 13 | + return this.history.length > this.currentIndex + 1 | |
| 14 | + } | |
| 15 | + | |
| 16 | + init (store) { | |
| 17 | + this.store = store | |
| 18 | + } | |
| 19 | + | |
| 20 | + addState (state) { | |
| 21 | + // may be we have to remove redo steps | |
| 22 | + if (this.currentIndex + 1 < this.history.length) { | |
| 23 | + this.history.splice(this.currentIndex + 1) | |
| 24 | + } | |
| 25 | + this.history.push(state) | |
| 26 | + this.currentIndex++ | |
| 27 | + } | |
| 28 | + | |
| 29 | + undo () { | |
| 30 | + if (!this.canUndo) return | |
| 31 | + const prevState = this.history[this.currentIndex - 1] | |
| 32 | + // take a copy of the history state | |
| 33 | + // because it would be changed during store mutations | |
| 34 | + // what would corrupt the undo-redo-history | |
| 35 | + // (same on redo) | |
| 36 | + this.store.replaceState(cloneDeep(prevState)) | |
| 37 | + this.currentIndex-- | |
| 38 | + } | |
| 39 | + | |
| 40 | + redo () { | |
| 41 | + if (!this.canRedo) return | |
| 42 | + const nextState = this.history[this.currentIndex + 1] | |
| 43 | + this.store.replaceState(cloneDeep(nextState)) | |
| 44 | + this.currentIndex++ | |
| 45 | + } | |
| 46 | +} | |
| 47 | + | |
| 48 | +const undoRedoHistory = new UndoRedoHistory() | |
| 49 | + | |
| 50 | +export default undoRedoHistory | ... | ... |
front-end/h5/src/store/plugins/undo-redo/index.js
0 → 100644
| 1 | + | |
| 2 | +import { cloneDeep } from 'lodash' | |
| 3 | +import undoRedoHistory from './History' | |
| 4 | +const unRecordHistoryMutationTypes = ['element/setElementCommonStyle'] | |
| 5 | + | |
| 6 | +const undoRedoPlugin = (store) => { | |
| 7 | + // initialize and save the starting stage | |
| 8 | + undoRedoHistory.init(store) | |
| 9 | + let firstState = cloneDeep(store.state) | |
| 10 | + undoRedoHistory.addState(firstState) | |
| 11 | + | |
| 12 | + store.subscribe((mutation, state) => { | |
| 13 | + const { type } = mutation | |
| 14 | + if (unRecordHistoryMutationTypes.includes(type)) return | |
| 15 | + // is called AFTER every mutation | |
| 16 | + undoRedoHistory.addState(cloneDeep(state)) | |
| 17 | + }) | |
| 18 | +} | |
| 19 | + | |
| 20 | +export default undoRedoPlugin | ... | ... |