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,6 +45,7 @@ export default { | ||
| 45 | 'setEditingElement', // -> this.foo() | 45 | 'setEditingElement', // -> this.foo() |
| 46 | 'setElementPosition', // -> this.foo() | 46 | 'setElementPosition', // -> this.foo() |
| 47 | 'setElementShape', // -> this.foo() | 47 | 'setElementShape', // -> this.foo() |
| 48 | + 'recordElementRect', // -> this.foo() | ||
| 48 | 'elementManager' | 49 | 'elementManager' |
| 49 | ]), | 50 | ]), |
| 50 | // TODO #!zh: 优化代码 | 51 | // TODO #!zh: 优化代码 |
| @@ -196,6 +197,12 @@ export default { | @@ -196,6 +197,12 @@ export default { | ||
| 196 | this.setElementPosition(pos) | 197 | this.setElementPosition(pos) |
| 197 | }} | 198 | }} |
| 198 | handleElementMoveProp={this.handleElementMove} | 199 | handleElementMoveProp={this.handleElementMove} |
| 200 | + handleElementMouseUpProp={() => { | ||
| 201 | + this.recordElementRect() | ||
| 202 | + }} | ||
| 203 | + handlePointMouseUpProp={() => { | ||
| 204 | + this.recordElementRect() | ||
| 205 | + }} | ||
| 199 | > | 206 | > |
| 200 | {h(element.name, data)} | 207 | {h(element.name, data)} |
| 201 | </Shape> | 208 | </Shape> |
front-end/h5/src/components/core/editor/index.js
| 1 | import Vue from 'vue' | 1 | import Vue from 'vue' |
| 2 | import { mapState, mapActions } from 'vuex' | 2 | import { mapState, mapActions } from 'vuex' |
| 3 | // import Element from '../models/element' | 3 | // import Element from '../models/element' |
| 4 | +import undoRedoHistory from '../../../store/plugins/undo-redo/History' | ||
| 4 | 5 | ||
| 5 | import '../styles/index.scss' | 6 | import '../styles/index.scss' |
| 6 | 7 | ||
| @@ -79,6 +80,12 @@ export default { | @@ -79,6 +80,12 @@ export default { | ||
| 79 | defaultSelectedKeys={['2']} | 80 | defaultSelectedKeys={['2']} |
| 80 | style={{ lineHeight: '64px', float: 'right', background: 'transparent' }} | 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 | <a-menu-item key="1" class="transparent-bg"><a-button type="primary" size="small">预览</a-button></a-menu-item> | 89 | <a-menu-item key="1" class="transparent-bg"><a-button type="primary" size="small">预览</a-button></a-menu-item> |
| 83 | <a-menu-item key="2" class="transparent-bg"><a-button size="small">保存</a-button></a-menu-item> | 90 | <a-menu-item key="2" class="transparent-bg"><a-button size="small">保存</a-button></a-menu-item> |
| 84 | <a-menu-item key="3" class="transparent-bg"><a-button size="small">发布</a-button></a-menu-item> | 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,7 +13,7 @@ const directionKey = { | ||
| 13 | const points = ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b'] | 13 | const points = ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b'] |
| 14 | 14 | ||
| 15 | export default { | 15 | export default { |
| 16 | - props: ['defaultPosition', 'active', 'handleMousedownProp', 'handleElementMoveProp', 'handlePointMoveProp'], | 16 | + props: ['defaultPosition', 'active', 'handleMousedownProp', 'handleElementMoveProp', 'handlePointMoveProp', 'handleElementMouseUpProp', 'handlePointMouseUpProp'], |
| 17 | computed: { | 17 | computed: { |
| 18 | position () { | 18 | position () { |
| 19 | return { ...this.defaultPosition } | 19 | return { ...this.defaultPosition } |
front-end/h5/src/store/index.js
| 1 | import Vue from 'vue' | 1 | import Vue from 'vue' |
| 2 | import Vuex from 'vuex' | 2 | import Vuex from 'vuex' |
| 3 | +import undoRedoPlugin from './plugins/undo-redo/index' | ||
| 3 | import editor from './modules/editor' | 4 | import editor from './modules/editor' |
| 4 | import user from './modules/user' | 5 | import user from './modules/user' |
| 5 | import visible from './modules/visible' | 6 | import visible from './modules/visible' |
| @@ -24,5 +25,6 @@ export default new Vuex.Store({ | @@ -24,5 +25,6 @@ export default new Vuex.Store({ | ||
| 24 | visible, | 25 | visible, |
| 25 | loading, | 26 | loading, |
| 26 | element | 27 | element |
| 27 | - } | 28 | + }, |
| 29 | + plugins: [undoRedoPlugin] | ||
| 28 | }) | 30 | }) |
front-end/h5/src/store/modules/element.js
| @@ -22,6 +22,9 @@ const actions = { | @@ -22,6 +22,9 @@ const actions = { | ||
| 22 | setElementShape ({ commit }, payload) { | 22 | setElementShape ({ commit }, payload) { |
| 23 | commit('setElementCommonStyle', payload) | 23 | commit('setElementCommonStyle', payload) |
| 24 | }, | 24 | }, |
| 25 | + recordElementRect ({ commit }, payload = {}) { | ||
| 26 | + commit('recordRect', payload) | ||
| 27 | + }, | ||
| 25 | elementManager ({ commit }, payload) { | 28 | elementManager ({ commit }, payload) { |
| 26 | commit('elementManager', payload) | 29 | commit('elementManager', payload) |
| 27 | } | 30 | } |
| @@ -58,6 +61,9 @@ const mutations = { | @@ -58,6 +61,9 @@ const mutations = { | ||
| 58 | break | 61 | break |
| 59 | default: | 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 |