From dde00d93a8c288c2deafb3e2bc0f2cc837f47b0e Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sun, 10 Dec 2017 23:12:34 +0100 Subject: [PATCH 01/18] Start UndoManager --- src/editor/model/Editor.js | 16 ++- src/undo_manager/index.js | 207 +++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 src/undo_manager/index.js diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 5924e7172..8e53e51c2 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -57,9 +57,7 @@ module.exports = Backbone.Model.extend({ this.config.components = c.el.innerHTML; // Load modules - deps.forEach(function(name){ - this.loadModule(name); - }, this); + deps.forEach(name => this.loadModule(name)); this.initUndoManager(); @@ -151,22 +149,20 @@ module.exports = Backbone.Model.extend({ // Check if module is storable var sm = this.get('StorageManager'); - if(Mod.storageKey && Mod.store && Mod.load && sm){ + + if (Mod.storageKey && Mod.store && Mod.load && sm) { cfg.stm = sm; var storables = this.get('storables'); storables.push(Mod); this.set('storables', storables); } + cfg.em = this; Mod.init({ ...cfg }); // Bind the module to the editor model if public - if(!Mod.private) - this.set(Mod.name, Mod); - - if(Mod.onLoad) - this.get('toLoad').push(Mod); - + !Mod.private && this.set(Mod.name, Mod); + Mod.onLoad && this.get('toLoad').push(Mod); this.get('modules').push(Mod); return this; }, diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js new file mode 100644 index 000000000..7fd5a431c --- /dev/null +++ b/src/undo_manager/index.js @@ -0,0 +1,207 @@ +/** + * This module allows to create shortcuts for functions and commands (via command id) + * + * You can access the module in this way + * ```js + * const undoManager = editor.UndoManager; + * ``` + * + */ +import UndoManager from 'backbone-undo'; + +module.exports = () => { + let config; + let um; + const configDef = {}; + const keymaps = {}; + + /* + const canvas = this.get('Canvas'); + + if (this.um) { + return; + } + + var cmp = this.get('DomComponents'); + if(cmp && this.config.undoManager) { + var that = this; + this.um = new UndoManager({ + register: [cmp.getComponents(), this.get('CssComposer').getAll()], + track: true + }); + this.UndoManager = this.um; + this.set('UndoManager', this.um); + + key('⌘+z, ctrl+z', () => { + if (canvas.isInputFocused()) { + return; + } + + that.um.undo(true); + that.trigger('component:update'); + }); + + key('⌘+shift+z, ctrl+shift+z', () => { + if (canvas.isInputFocused()) { + return; + } + that.um.redo(true); + that.trigger('component:update'); + }); + + var beforeCache; + const customUndoType = { + on: function (model, value, opts) { + var opt = opts || {}; + if(!beforeCache){ + beforeCache = model.previousAttributes(); + } + if (opt && opt.avoidStore) { + return; + } else { + var obj = { + object: model, + before: beforeCache, + after: model.toJSON() + }; + beforeCache = null; + return obj; + } + }, + undo: function (model, bf, af, opt) { + model.set(bf); + // Update also inputs inside Style Manager + that.trigger('change:selectedComponent'); + }, + redo: function (model, bf, af, opt) { + model.set(af); + // Update also inputs inside Style Manager + that.trigger('change:selectedComponent'); + } + }; + + UndoManager.removeUndoType("change"); + UndoManager.addUndoType("change:style", customUndoType); + UndoManager.addUndoType("change:attributes", customUndoType); + UndoManager.addUndoType("change:content", customUndoType); + UndoManager.addUndoType("change:src", customUndoType); + } + */ + + return { + + name: 'UndoManager', + + + /** + * Initialize module + * @param {Object} config Configurations + * @private + */ + init(opts = {}) { + config = { ...opts, ...configDef }; + this.em = config.em; + um = new UndoManager({ track: true, register: [] }); + return this; + }, + + + /** + * Get module configurations + * @return {Object} Configuration object + */ + getConfig() { + return config; + }, + + + /** + * Add an entity (Model/Collection) to track changes + * @param {Model|Collection} entity Entity to track + */ + add(entity) { + um.register(entity); + }, + + + /** + * Remove and stop tracking the entity (Model/Collection) + * @param {Model|Collection} entity Entity to remove + */ + remove(entity) { + um.unregister(entity); + }, + + + /** + * Remove all entities + */ + removeAll() { + um.unregisterAll() + }, + + + /** + * Start/resume tracking changes + */ + start() { + em.startTracking(); + }, + + + /** + * Stop tracking changes + */ + stop() { + em.stopTracking(); + }, + + + /** + * Undo last change + */ + undo() { + em.undo(1); + }, + + + /** + * Undo all changes + */ + undoAll() { + em.undoAll(); + }, + + + /** + * Redo last change + */ + redo() { + em.redo(1); + }, + + + /** + * Redo all changes + */ + redoAll() { + em.redoAll(); + }, + + + /** + * Get stack of changes + * @return {Array} + */ + getStack() { + + }, + + /** + * Clear the stack + */ + clear() { + + } + }; +}; From faa7fb839af75f3726eeab240d57d6fed071b9ba Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 00:14:30 +0100 Subject: [PATCH 02/18] Start building the init method --- src/dom_components/index.js | 8 +++- src/editor/config/config.js | 6 +-- src/editor/model/Editor.js | 12 +++--- src/grapesjs/config/config.js | 3 -- src/undo_manager/index.js | 74 ++++++++++++++++++++++++++++++----- 5 files changed, 79 insertions(+), 24 deletions(-) diff --git a/src/dom_components/index.js b/src/dom_components/index.js index 5bb9bde82..40001520e 100644 --- a/src/dom_components/index.js +++ b/src/dom_components/index.js @@ -34,6 +34,7 @@ */ module.exports = () => { var c = {}; + let em; const defaults = require('./config/config'); const Component = require('./model/Component'); const ComponentView = require('./view/ComponentView'); @@ -166,7 +167,7 @@ module.exports = () => { */ init(config) { c = config || {}; - const em = c.em; + em = c.em; if (em) { c.components = em.config.components || c.components; @@ -228,7 +229,10 @@ module.exports = () => { * @private */ onLoad() { - this.getComponents().reset(c.components); + const comps = this.getComponents(); + comps.reset(c.components); + const um = em && em.get('UndoManager'); + um && um.add(comps); }, /** diff --git a/src/editor/config/config.js b/src/editor/config/config.js index 328f6b861..4e6e44742 100644 --- a/src/editor/config/config.js +++ b/src/editor/config/config.js @@ -11,9 +11,6 @@ module.exports = { // Show an alert before unload the page with unsaved changes noticeOnUnload: true, - // Enable/Disable undo manager - undoManager: true, - // Show paddings and margins showOffsets: false, @@ -80,6 +77,9 @@ module.exports = { // Dom element el: '', + // Configurations for Undo Manager + undoManager: {}, + //Configurations for Asset Manager assetManager: {}, diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 8e53e51c2..610d8040a 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -3,6 +3,7 @@ import { isUndefined, defaults } from 'underscore'; const deps = [ require('utils'), require('keymaps'), + require('undo_manager'), require('storage_manager'), require('device_manager'), require('parser'), @@ -58,9 +59,7 @@ module.exports = Backbone.Model.extend({ // Load modules deps.forEach(name => this.loadModule(name)); - - this.initUndoManager(); - + //this.initUndoManager(); this.on('change:selectedComponent', this.componentSelected, this); this.on('change:changesCount', this.updateChanges, this); }, @@ -97,8 +96,8 @@ module.exports = Backbone.Model.extend({ // I've initialized undo manager in initialize() because otherwise the // editor will unable to fetch the instance via 'editor.UndoManager' but // I need to cleare the stack now as it was dirtied by 'onLoad' method - this.um.clear(); - this.initUndoManager(); + //this.um && this.um.clear(); + //this.initUndoManager(); this.get('modules').forEach(module => module.postLoad && module.postLoad(this) ); @@ -231,7 +230,7 @@ module.exports = Backbone.Model.extend({ /** * Initialize Undo manager * @private - * */ + * * initUndoManager() { const canvas = this.get('Canvas'); @@ -304,6 +303,7 @@ module.exports = Backbone.Model.extend({ UndoManager.addUndoType("change:src", customUndoType); } }, + */ /** * Callback on component selection diff --git a/src/grapesjs/config/config.js b/src/grapesjs/config/config.js index 941316798..004f9be89 100644 --- a/src/grapesjs/config/config.js +++ b/src/grapesjs/config/config.js @@ -18,9 +18,6 @@ module.exports = { // Enable/Disable the possibility to copy(ctrl + c) & paste(ctrl + v) components copyPaste: true, - // Enable/Disable undo manager - undoManager: true, - // Storage Manager storageManager: {}, diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index 7fd5a431c..d25628da6 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -1,5 +1,5 @@ /** - * This module allows to create shortcuts for functions and commands (via command id) + * This module allows to manage the stack of changes applied on canvas * * You can access the module in this way * ```js @@ -10,8 +10,10 @@ import UndoManager from 'backbone-undo'; module.exports = () => { + let em; let config; let um; + let beforeCache; const configDef = {}; const keymaps = {}; @@ -100,8 +102,42 @@ module.exports = () => { */ init(opts = {}) { config = { ...opts, ...configDef }; - this.em = config.em; + em = config.em; + this.em = em; um = new UndoManager({ track: true, register: [] }); + um.changeUndoType('change', { condition: false }); + const updated = () => em.trigger('change:selectedComponent'); + const customUndoType = { + on(object, value, opt = {}) { + !beforeCache && (beforeCache = object.previousAttributes()); + + if (opt.avoidStore) { + return; + } else { + const result = { + object, + before: beforeCache, + after: object.toJSON() + }; + beforeCache = null; + return result; + } + }, + + undo(model, bf, af, opt) { + model.set(bf); + updated(); + }, + + redo(model, bf, af, opt) { + model.set(af); + updated(); + } + }; + + const events = ['style', 'attributes', 'content', 'src']; + events.forEach(ev => um.addUndoType(`change:${ev}`, customUndoType)); + return this; }, @@ -145,7 +181,7 @@ module.exports = () => { * Start/resume tracking changes */ start() { - em.startTracking(); + um.startTracking(); }, @@ -153,7 +189,7 @@ module.exports = () => { * Stop tracking changes */ stop() { - em.stopTracking(); + um.stopTracking(); }, @@ -161,7 +197,7 @@ module.exports = () => { * Undo last change */ undo() { - em.undo(1); + um.undo(1); }, @@ -169,7 +205,7 @@ module.exports = () => { * Undo all changes */ undoAll() { - em.undoAll(); + um.undoAll(); }, @@ -177,7 +213,7 @@ module.exports = () => { * Redo last change */ redo() { - em.redo(1); + um.redo(1); }, @@ -185,7 +221,25 @@ module.exports = () => { * Redo all changes */ redoAll() { - em.redoAll(); + um.redoAll(); + }, + + + /** + * Checks if there is an available undo + * @return {Boolean} + */ + hasUndo() { + return um.isAvailable('undo'); + }, + + + /** + * Checks if there is an available redo + * @return {Boolean} + */ + hasRedo() { + return um.isAvailable('redo'); }, @@ -194,14 +248,14 @@ module.exports = () => { * @return {Array} */ getStack() { - + return um.stack; }, /** * Clear the stack */ clear() { - + um.clear(); } }; }; From 6a6d6473dc43d323cd2bff35d2bf6873e626d008 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 00:19:06 +0100 Subject: [PATCH 03/18] Don't need the boolean value in `core:undo` and `core:redo` --- src/commands/index.js | 4 ++-- src/undo_manager/index.js | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/commands/index.js b/src/commands/index.js index 52afd8a55..acc613466 100644 --- a/src/commands/index.js +++ b/src/commands/index.js @@ -200,8 +200,8 @@ module.exports = () => { }; // Core commands - defaultCommands['core:undo'] = e => e.UndoManager.undo(1); - defaultCommands['core:redo'] = e => e.UndoManager.redo(1); + defaultCommands['core:undo'] = e => e.UndoManager.undo(); + defaultCommands['core:redo'] = e => e.UndoManager.redo(); defaultCommands['core:canvas-clear'] = e => { e.DomComponents.clear(); e.CssComposer.clear(); diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index d25628da6..6b46f9ecc 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -106,7 +106,11 @@ module.exports = () => { this.em = em; um = new UndoManager({ track: true, register: [] }); um.changeUndoType('change', { condition: false }); - const updated = () => em.trigger('change:selectedComponent'); + const updated = () => { + em.trigger('change:selectedComponent'); + em.trigger('change:canvasOffset'); + console.log('updated'); + }; const customUndoType = { on(object, value, opt = {}) { !beforeCache && (beforeCache = object.previousAttributes()); From 06a7abcff4aa85e6f2b5a43e66d3b62767a6b381 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:07:45 +0100 Subject: [PATCH 04/18] Remove `rmComponents` method from Editor --- src/block_manager/view/BlocksView.js | 2 -- src/commands/view/InsertCustom.js | 3 -- src/dom_components/index.js | 41 ++++++++++++++++++++++++++-- src/editor/model/Editor.js | 11 ++++---- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/block_manager/view/BlocksView.js b/src/block_manager/view/BlocksView.js index 75cfdf25c..e14663450 100644 --- a/src/block_manager/view/BlocksView.js +++ b/src/block_manager/view/BlocksView.js @@ -84,8 +84,6 @@ module.exports = Backbone.View.extend({ model.set('activeOnRender', 0); } - // Register all its components (eg. for the Undo Manager) - em.initChildrenComp(model); em.trigger('block:drag:stop', model); } }, diff --git a/src/commands/view/InsertCustom.js b/src/commands/view/InsertCustom.js index 939578a8b..02bd7fa06 100644 --- a/src/commands/view/InsertCustom.js +++ b/src/commands/view/InsertCustom.js @@ -47,9 +47,6 @@ module.exports = _.extend({}, CreateComponent, { if(!model) return; - if(this.em) - this.em.editor.initChildrenComp(model); - this.afterInsert(model, this); }, diff --git a/src/dom_components/index.js b/src/dom_components/index.js index 40001520e..b61b8a19d 100644 --- a/src/dom_components/index.js +++ b/src/dom_components/index.js @@ -231,8 +231,8 @@ module.exports = () => { onLoad() { const comps = this.getComponents(); comps.reset(c.components); - const um = em && em.get('UndoManager'); - um && um.add(comps); + //const um = em && em.get('UndoManager'); + //um && um.add(comps); }, /** @@ -241,9 +241,44 @@ module.exports = () => { * @private */ postLoad(em) { - em.initChildrenComp(this.getWrapper()); + this.handleChanges(this.getWrapper(), null, { avoidStore: 1 }); }, + + /** + * Handle component changes + * @private + */ + handleChanges(model, value, opts = {}) { + const comps = model.get('components'); + const um = em.get('UndoManager'); + const handleUpdates = em.handleUpdates.bind(em); + um && um.add(model) && comps && um.add(comps); + const evn = 'change:style change:content change:attributes change:src'; + + [ [comps, 'add', this.handleChanges], + [comps, 'remove', this.handleRemoves], + [model, evn, handleUpdates], + [model.get('classes'), 'add remove', handleUpdates], + ].forEach(els => { + em.stopListening(els[0], els[1], els[2]); + em.listenTo(els[0], els[1], els[2]); + }); + + !opts.avoidStore && handleUpdates('', '', opts); + comps.each(model => this.handleChanges(model)); + }, + + + /** + * Triggered when some component is removed + * @private + * */ + handleRemoves(model, value, opts = {}) { + !opts.avoidStore && em.handleUpdates(model, value, opts); + }, + + /** * Load components from the passed object, if the object is empty will try to fetch them * autonomously from the selected storage diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 610d8040a..db4a4d824 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -358,16 +358,15 @@ module.exports = Backbone.Model.extend({ * Init stuff like storage for already existing elements * @param {Object} model * @private - */ + * initChildrenComp(model) { var comps = model.get('components'); this.updateComponents(model, null, { avoidStore : 1 }); - comps.each(function(md) { + comps.each(md => { this.initChildrenComp(md); - if(this.um) - this.um.register(md); - }, this); - }, + this.um && this.um.register(md); + }); + },*/ /** * Triggered when some component is removed updated From 268060f5ba2911213def4d02bc892f0f15b05785 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:08:44 +0100 Subject: [PATCH 05/18] Remove `initChildrenComp` method from Editor --- src/editor/model/Editor.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index db4a4d824..69014dcb8 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -354,33 +354,6 @@ module.exports = Backbone.Model.extend({ this.handleUpdates(model, val, opt); }, - /** - * Init stuff like storage for already existing elements - * @param {Object} model - * @private - * - initChildrenComp(model) { - var comps = model.get('components'); - this.updateComponents(model, null, { avoidStore : 1 }); - comps.each(md => { - this.initChildrenComp(md); - this.um && this.um.register(md); - }); - },*/ - - /** - * Triggered when some component is removed updated - * @param {Object} model - * @param {Mixed} val Value - * @param {Object} opt Options - * @private - * */ - rmComponents(model, val, opt) { - var avSt = opt ? opt.avoidStore : 0; - - if(!avSt) - this.handleUpdates(model, val, opt); - }, /** * Returns model of the selected component From 4ef8882eee569e9f08e6f4e6d10e8411d62f0cf9 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:10:21 +0100 Subject: [PATCH 06/18] Remove `updateComponents` method from Editor --- src/editor/model/Editor.js | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 69014dcb8..4c6636504 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -321,39 +321,6 @@ module.exports = Backbone.Model.extend({ } }, - /** - * Triggered when components are updated - * @param {Object} model - * @param {Mixed} val Value - * @param {Object} opt Options - * @private - * */ - updateComponents(model, val, opt) { - var comps = model.get('components'), - classes = model.get('classes'), - avSt = opt ? opt.avoidStore : 0; - - // Observe component with Undo Manager - if(this.um) - this.um.register(comps); - - // Call stopListening for not creating nested listeners - this.stopListening(comps, 'add', this.updateComponents); - this.stopListening(comps, 'remove', this.rmComponents); - this.listenTo(comps, 'add', this.updateComponents); - this.listenTo(comps, 'remove', this.rmComponents); - - this.stopListening(classes, 'add remove', this.handleUpdates); - this.listenTo(classes, 'add remove', this.handleUpdates); - - var evn = 'change:style change:content change:attributes change:src'; - this.stopListening(model, evn, this.handleUpdates); - this.listenTo(model, evn, this.handleUpdates); - - if(!avSt) - this.handleUpdates(model, val, opt); - }, - /** * Returns model of the selected component From e553a9b949d76eab8958d9b892740468a335aa40 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:20:06 +0100 Subject: [PATCH 07/18] Bind context for handleChanges and handleRemoves --- src/dom_components/index.js | 8 +++++--- src/editor/model/Editor.js | 37 ------------------------------------- 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/src/dom_components/index.js b/src/dom_components/index.js index b61b8a19d..a287a28d7 100644 --- a/src/dom_components/index.js +++ b/src/dom_components/index.js @@ -253,12 +253,14 @@ module.exports = () => { const comps = model.get('components'); const um = em.get('UndoManager'); const handleUpdates = em.handleUpdates.bind(em); + const handleChanges = this.handleChanges.bind(this); + const handleRemoves = this.handleRemoves.bind(this); um && um.add(model) && comps && um.add(comps); const evn = 'change:style change:content change:attributes change:src'; - [ [comps, 'add', this.handleChanges], - [comps, 'remove', this.handleRemoves], - [model, evn, handleUpdates], + [ [model, evn, handleUpdates], + [comps, 'add', handleChanges], + [comps, 'remove', handleRemoves], [model.get('classes'), 'add remove', handleUpdates], ].forEach(els => { em.stopListening(els[0], els[1], els[2]); diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 4c6636504..e440f22cf 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -264,43 +264,6 @@ module.exports = Backbone.Model.extend({ that.um.redo(true); that.trigger('component:update'); }); - - var beforeCache; - const customUndoType = { - on: function (model, value, opts) { - var opt = opts || {}; - if(!beforeCache){ - beforeCache = model.previousAttributes(); - } - if (opt && opt.avoidStore) { - return; - } else { - var obj = { - object: model, - before: beforeCache, - after: model.toJSON() - }; - beforeCache = null; - return obj; - } - }, - undo: function (model, bf, af, opt) { - model.set(bf); - // Update also inputs inside Style Manager - that.trigger('change:selectedComponent'); - }, - redo: function (model, bf, af, opt) { - model.set(af); - // Update also inputs inside Style Manager - that.trigger('change:selectedComponent'); - } - }; - - UndoManager.removeUndoType("change"); - UndoManager.addUndoType("change:style", customUndoType); - UndoManager.addUndoType("change:attributes", customUndoType); - UndoManager.addUndoType("change:content", customUndoType); - UndoManager.addUndoType("change:src", customUndoType); } }, */ From e0ea018d02b4e5cedbeb867f5dcfbc97ce4b4f9d Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:36:17 +0100 Subject: [PATCH 08/18] Add default `core:undo` and `core:redo` keymaps --- src/dom_components/index.js | 5 +---- src/keymaps/index.js | 30 +++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/dom_components/index.js b/src/dom_components/index.js index a287a28d7..e1d2158bd 100644 --- a/src/dom_components/index.js +++ b/src/dom_components/index.js @@ -229,10 +229,7 @@ module.exports = () => { * @private */ onLoad() { - const comps = this.getComponents(); - comps.reset(c.components); - //const um = em && em.get('UndoManager'); - //um && um.add(comps); + this.getComponents().reset(c.components); }, /** diff --git a/src/keymaps/index.js b/src/keymaps/index.js index 401dd2353..8d4277ab3 100644 --- a/src/keymaps/index.js +++ b/src/keymaps/index.js @@ -12,9 +12,21 @@ import { isString } from 'underscore'; const keymaster = require('keymaster'); module.exports = () => { + let em; let config; - const configDef = {}; const keymaps = {}; + const configDef = { + defaults: { + 'core:undo': { + keys: '⌘+z, ctrl+z', + handler: 'core:undo', + }, + 'core:redo': { + keys: '⌘+shift+z, ctrl+shift+z', + handler: 'core:redo', + } + } + }; return { @@ -39,11 +51,23 @@ module.exports = () => { * @private */ init(opts = {}) { - config = { ...opts, ...configDef }; - this.em = config.em; + config = { ...configDef, ...opts }; + em = config.em; + this.em = em; return this; }, + + onLoad() { + const defKeys = config.defaults; + + for (let id in defKeys) { + const value = defKeys[id]; + this.add(id, value.keys, value.handler); + } + }, + + /** * Add new keymap * @param {string} id Keymap id From cb19de3cbd913a2eb8fef22d2f35c6be5b19023a Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:40:44 +0100 Subject: [PATCH 09/18] Check focused inputs in undo/redo --- src/editor/model/Editor.js | 23 ----------------------- src/undo_manager/index.js | 2 ++ 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index e440f22cf..2bf23328b 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -232,11 +232,6 @@ module.exports = Backbone.Model.extend({ * @private * * initUndoManager() { - const canvas = this.get('Canvas'); - - if (this.um) { - return; - } var cmp = this.get('DomComponents'); if(cmp && this.config.undoManager) { @@ -245,25 +240,7 @@ module.exports = Backbone.Model.extend({ register: [cmp.getComponents(), this.get('CssComposer').getAll()], track: true }); - this.UndoManager = this.um; - this.set('UndoManager', this.um); - - key('⌘+z, ctrl+z', () => { - if (canvas.isInputFocused()) { - return; - } - that.um.undo(true); - that.trigger('component:update'); - }); - - key('⌘+shift+z, ctrl+shift+z', () => { - if (canvas.isInputFocused()) { - return; - } - that.um.redo(true); - that.trigger('component:update'); - }); } }, */ diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index 6b46f9ecc..de701818f 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -201,6 +201,7 @@ module.exports = () => { * Undo last change */ undo() { + if (em.get('Canvas').isInputFocused()) return; um.undo(1); }, @@ -217,6 +218,7 @@ module.exports = () => { * Redo last change */ redo() { + if (em.get('Canvas').isInputFocused()) return; um.redo(1); }, From c7ff050a99a6ac56c6945c573eb8e3953e70c029 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:58:18 +0100 Subject: [PATCH 10/18] Start tracking rules in undo manager --- src/css_composer/index.js | 22 +++++++++++++++++++++- src/editor/model/Editor.js | 6 +++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/css_composer/index.js b/src/css_composer/index.js index d3cc7aa4a..20b4e3304 100644 --- a/src/css_composer/index.js +++ b/src/css_composer/index.js @@ -91,15 +91,35 @@ module.exports = () => { rules.add(c.rules); }, + /** * Do stuff after load * @param {Editor} em * @private */ postLoad(em) { - em.listenRules(this.getAll()); + const ev = 'add remove'; + const rules = this.getAll(); + const um = em.get('UndoManager'); + um && um.add(rules); + em.stopListening(rules, ev, this.handleChange); + em.listenTo(rules, ev, this.handleChange); + rules.each(rule => this.handleChange(rule)); + }, + + + /** + * Handle rule changes + * @private + */ + handleChange(model) { + const ev = 'change:style'; + const handleUpdates = em.handleUpdates.bind(em); + em.stopListening(model, ev, handleUpdates); + em.listenTo(model, ev, handleUpdates); }, + /** * Load data from the passed object, if the object is empty will try to fetch them * autonomously from the storage manager. diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 2bf23328b..91e1d551d 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -186,7 +186,7 @@ module.exports = Backbone.Model.extend({ * Listen for new rules * @param {Object} collection * @private - */ + * listenRules(collection) { this.stopListening(collection, 'add remove', this.listenRule); this.listenTo(collection, 'add remove', this.listenRule); @@ -199,11 +199,11 @@ module.exports = Backbone.Model.extend({ * Listen for rule changes * @param {Object} model * @private - */ + * listenRule(model) { this.stopListening(model, 'change:style', this.handleUpdates); this.listenTo(model, 'change:style', this.handleUpdates); - }, + },*/ /** * This method handles updates on the editor and tries to store them From e806ebc3c9170862b8592093382dbb4574441d22 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 01:59:12 +0100 Subject: [PATCH 11/18] Clean Editor from unused methods --- src/editor/model/Editor.js | 40 -------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 91e1d551d..ef09b05b9 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -182,29 +182,6 @@ module.exports = Backbone.Model.extend({ }, - /** - * Listen for new rules - * @param {Object} collection - * @private - * - listenRules(collection) { - this.stopListening(collection, 'add remove', this.listenRule); - this.listenTo(collection, 'add remove', this.listenRule); - collection.each(function(model){ - this.listenRule(model); - }, this); - }, - - /** - * Listen for rule changes - * @param {Object} model - * @private - * - listenRule(model) { - this.stopListening(model, 'change:style', this.handleUpdates); - this.listenTo(model, 'change:style', this.handleUpdates); - },*/ - /** * This method handles updates on the editor and tries to store them * if requested and if the changesCount is exceeded @@ -227,23 +204,6 @@ module.exports = Backbone.Model.extend({ }, 0); }, - /** - * Initialize Undo manager - * @private - * * - initUndoManager() { - - var cmp = this.get('DomComponents'); - if(cmp && this.config.undoManager) { - var that = this; - this.um = new UndoManager({ - register: [cmp.getComponents(), this.get('CssComposer').getAll()], - track: true - }); - - } - }, - */ /** * Callback on component selection From 58b732bdef4d85f55d890c453547a031f2affca4 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 02:11:32 +0100 Subject: [PATCH 12/18] Remove UndoManager references in Editor --- src/editor/model/Editor.js | 7 --- src/undo_manager/index.js | 88 ++------------------------------------ 2 files changed, 4 insertions(+), 91 deletions(-) diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index ef09b05b9..d443bd277 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -23,7 +23,6 @@ const deps = [ ]; const Backbone = require('backbone'); -const UndoManager = require('backbone-undo'); const key = require('keymaster'); let timedInterval; @@ -59,7 +58,6 @@ module.exports = Backbone.Model.extend({ // Load modules deps.forEach(name => this.loadModule(name)); - //this.initUndoManager(); this.on('change:selectedComponent', this.componentSelected, this); this.on('change:changesCount', this.updateChanges, this); }, @@ -93,11 +91,6 @@ module.exports = Backbone.Model.extend({ // Stuff to do post load (eg. init undo manager for loaded components) const postLoad = () => { - // I've initialized undo manager in initialize() because otherwise the - // editor will unable to fetch the instance via 'editor.UndoManager' but - // I need to cleare the stack now as it was dirtied by 'onLoad' method - //this.um && this.um.clear(); - //this.initUndoManager(); this.get('modules').forEach(module => module.postLoad && module.postLoad(this) ); diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index de701818f..df26561b6 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -11,84 +11,10 @@ import UndoManager from 'backbone-undo'; module.exports = () => { let em; - let config; let um; + let config; let beforeCache; const configDef = {}; - const keymaps = {}; - - /* - const canvas = this.get('Canvas'); - - if (this.um) { - return; - } - - var cmp = this.get('DomComponents'); - if(cmp && this.config.undoManager) { - var that = this; - this.um = new UndoManager({ - register: [cmp.getComponents(), this.get('CssComposer').getAll()], - track: true - }); - this.UndoManager = this.um; - this.set('UndoManager', this.um); - - key('⌘+z, ctrl+z', () => { - if (canvas.isInputFocused()) { - return; - } - - that.um.undo(true); - that.trigger('component:update'); - }); - - key('⌘+shift+z, ctrl+shift+z', () => { - if (canvas.isInputFocused()) { - return; - } - that.um.redo(true); - that.trigger('component:update'); - }); - - var beforeCache; - const customUndoType = { - on: function (model, value, opts) { - var opt = opts || {}; - if(!beforeCache){ - beforeCache = model.previousAttributes(); - } - if (opt && opt.avoidStore) { - return; - } else { - var obj = { - object: model, - before: beforeCache, - after: model.toJSON() - }; - beforeCache = null; - return obj; - } - }, - undo: function (model, bf, af, opt) { - model.set(bf); - // Update also inputs inside Style Manager - that.trigger('change:selectedComponent'); - }, - redo: function (model, bf, af, opt) { - model.set(af); - // Update also inputs inside Style Manager - that.trigger('change:selectedComponent'); - } - }; - - UndoManager.removeUndoType("change"); - UndoManager.addUndoType("change:style", customUndoType); - UndoManager.addUndoType("change:attributes", customUndoType); - UndoManager.addUndoType("change:content", customUndoType); - UndoManager.addUndoType("change:src", customUndoType); - } - */ return { @@ -105,12 +31,6 @@ module.exports = () => { em = config.em; this.em = em; um = new UndoManager({ track: true, register: [] }); - um.changeUndoType('change', { condition: false }); - const updated = () => { - em.trigger('change:selectedComponent'); - em.trigger('change:canvasOffset'); - console.log('updated'); - }; const customUndoType = { on(object, value, opt = {}) { !beforeCache && (beforeCache = object.previousAttributes()); @@ -130,17 +50,17 @@ module.exports = () => { undo(model, bf, af, opt) { model.set(bf); - updated(); }, redo(model, bf, af, opt) { model.set(af); - updated(); } }; const events = ['style', 'attributes', 'content', 'src']; events.forEach(ev => um.addUndoType(`change:${ev}`, customUndoType)); + um.changeUndoType('change', { condition: false }); + um.on('undo redo', () => em.trigger('change:selectedComponent change:canvasOffset')); return this; }, @@ -156,7 +76,7 @@ module.exports = () => { /** - * Add an entity (Model/Collection) to track changes + * Add an entity (Model/Collection) to track * @param {Model|Collection} entity Entity to track */ add(entity) { From ce986318b624060d9fd8dbf3bfef46f7fa1d669b Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 02:13:57 +0100 Subject: [PATCH 13/18] Remove keymaps references --- src/editor/model/Editor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index d443bd277..62f10f0c1 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -23,7 +23,6 @@ const deps = [ ]; const Backbone = require('backbone'); -const key = require('keymaster'); let timedInterval; require('utils/extender')({ From dbee6942b6825791c689b14454e771af59f4a69c Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 02:17:38 +0100 Subject: [PATCH 14/18] Update UndoManager interfaces in StyleManager --- src/style_manager/view/SectorsView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/style_manager/view/SectorsView.js b/src/style_manager/view/SectorsView.js index 754dcdd2a..89f03ade5 100644 --- a/src/style_manager/view/SectorsView.js +++ b/src/style_manager/view/SectorsView.js @@ -105,11 +105,11 @@ module.exports = Backbone.View.extend({ // selecting the component) and calling undo() it will remove the rule from // the collection, therefore updating it in style manager will not affect it // #268 - um.stopTracking(); + um.stop(); iContainer = cc.add(valid, state, media); iContainer.setStyle(model.getStyle()); model.setStyle({}); - um.startTracking(); + um.start(); } if (!iContainer) { From 4280280722c3295b86968e361030305f66ea8291 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 02:42:31 +0100 Subject: [PATCH 15/18] Clear the UndoManager on `loaded` event --- src/editor/index.js | 1 + src/editor/model/Editor.js | 7 +++---- src/undo_manager/index.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index e9e133b5b..fac28d08f 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -571,6 +571,7 @@ module.exports = config => { // Do post render stuff after the iframe is loaded otherwise it'll // be empty during tests em.on('loaded', () => { + this.UndoManager.clear(); em.get('modules').forEach(module => { module.postRender && module.postRender(editorView); }); diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index 62f10f0c1..6cb880b9d 100644 --- a/src/editor/model/Editor.js +++ b/src/editor/model/Editor.js @@ -88,11 +88,10 @@ module.exports = Backbone.Model.extend({ module.onLoad(); }); - // Stuff to do post load (eg. init undo manager for loaded components) + // Stuff to do post load const postLoad = () => { - this.get('modules').forEach(module => - module.postLoad && module.postLoad(this) - ); + const modules = this.get('modules'); + modules.forEach(module => module.postLoad && module.postLoad(this)); clb && clb(); }; diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index df26561b6..41908f78c 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -1,5 +1,5 @@ /** - * This module allows to manage the stack of changes applied on canvas + * This module allows to manage the stack of changes applied in canvas * * You can access the module in this way * ```js From 93783bb124a6b3d2c180d3b4f281d0abefc48cfe Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 04:06:00 +0100 Subject: [PATCH 16/18] Fix handleChanges --- src/dom_components/index.js | 7 ++++--- src/dom_components/view/ComponentTextView.js | 6 +++--- src/undo_manager/index.js | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/dom_components/index.js b/src/dom_components/index.js index e1d2158bd..5f65f1a32 100644 --- a/src/dom_components/index.js +++ b/src/dom_components/index.js @@ -247,12 +247,13 @@ module.exports = () => { * @private */ handleChanges(model, value, opts = {}) { - const comps = model.get('components'); + const comps = model.components(); const um = em.get('UndoManager'); const handleUpdates = em.handleUpdates.bind(em); const handleChanges = this.handleChanges.bind(this); const handleRemoves = this.handleRemoves.bind(this); - um && um.add(model) && comps && um.add(comps); + um && um.add(model); + um && comps && um.add(comps); const evn = 'change:style change:content change:attributes change:src'; [ [model, evn, handleUpdates], @@ -265,7 +266,7 @@ module.exports = () => { }); !opts.avoidStore && handleUpdates('', '', opts); - comps.each(model => this.handleChanges(model)); + comps.each(model => this.handleChanges(model, value, opts)); }, diff --git a/src/dom_components/view/ComponentTextView.js b/src/dom_components/view/ComponentTextView.js index d1118b1f1..2471e5645 100644 --- a/src/dom_components/view/ComponentTextView.js +++ b/src/dom_components/view/ComponentTextView.js @@ -60,14 +60,14 @@ module.exports = ComponentView.extend({ const content = this.getChildrenContainer().innerHTML; const comps = model.get('components'); comps.length && comps.reset(); + model.set('content', ''); // If there is a custom RTE the content is just baked staticly // inside 'content' if (rte.customRte) { // Avoid double content by removing its children components // and force to trigger change - model.set('content', '') - .set('content', content); + model.set('content', content); } else { const clean = model => { model.set({ @@ -82,7 +82,7 @@ module.exports = ComponentView.extend({ } // Avoid re-render on reset with silent option - model.set('content', '').trigger('change:content', model); + model.trigger('change:content', model); comps.add(content); comps.each(model => clean(model)); comps.trigger('resetNavigator'); diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index 41908f78c..b9774ba50 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -31,6 +31,7 @@ module.exports = () => { em = config.em; this.em = em; um = new UndoManager({ track: true, register: [] }); + um.changeUndoType('change', { condition: false }); const customUndoType = { on(object, value, opt = {}) { !beforeCache && (beforeCache = object.previousAttributes()); @@ -59,7 +60,6 @@ module.exports = () => { const events = ['style', 'attributes', 'content', 'src']; events.forEach(ev => um.addUndoType(`change:${ev}`, customUndoType)); - um.changeUndoType('change', { condition: false }); um.on('undo redo', () => em.trigger('change:selectedComponent change:canvasOffset')); return this; From 2ebfdc34e2cc748d70e07c4118c469103fde71db Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 10:25:52 +0100 Subject: [PATCH 17/18] Add `undo` and `redo` events --- src/editor/index.js | 2 ++ src/undo_manager/index.js | 1 + 2 files changed, 3 insertions(+) diff --git a/src/editor/index.js b/src/editor/index.js index fac28d08f..70ffbb6a1 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -51,6 +51,8 @@ * * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview')) * ## General * * `canvasScroll` - Triggered when the canvas is scrolle + * * `undo` - Undo executed + * * `redo` - Redo executed * * `load` - When the editor is loaded * * @param {Object} config Configurations diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index b9774ba50..6520898ba 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -61,6 +61,7 @@ module.exports = () => { const events = ['style', 'attributes', 'content', 'src']; events.forEach(ev => um.addUndoType(`change:${ev}`, customUndoType)); um.on('undo redo', () => em.trigger('change:selectedComponent change:canvasOffset')); + ['undo', 'redo'].forEach(ev => um.on(ev, () => em.trigger(ev))); return this; }, From df61a71374f6211a048339473e4b33d16c0a1331 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 11 Dec 2017 11:00:26 +0100 Subject: [PATCH 18/18] Update UndoManager JSDocs --- src/undo_manager/index.js | 67 +++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/src/undo_manager/index.js b/src/undo_manager/index.js index 6520898ba..34d52513a 100644 --- a/src/undo_manager/index.js +++ b/src/undo_manager/index.js @@ -3,7 +3,7 @@ * * You can access the module in this way * ```js - * const undoManager = editor.UndoManager; + * const um = editor.UndoManager; * ``` * */ @@ -70,6 +70,9 @@ module.exports = () => { /** * Get module configurations * @return {Object} Configuration object + * @example + * const config = um.getConfig(); + * // { ... } */ getConfig() { return config; @@ -78,83 +81,120 @@ module.exports = () => { /** * Add an entity (Model/Collection) to track + * Note: New Components and CSSRules will be added automatically * @param {Model|Collection} entity Entity to track + * @return {this} + * @example + * um.add(someModelOrCollection); */ add(entity) { um.register(entity); + return this; }, /** * Remove and stop tracking the entity (Model/Collection) * @param {Model|Collection} entity Entity to remove + * @return {this} + * @example + * um.remove(someModelOrCollection); */ remove(entity) { um.unregister(entity); + return this; }, /** * Remove all entities + * @return {this} + * @example + * um.removeAll(); */ removeAll() { - um.unregisterAll() + um.unregisterAll(); + return this; }, /** * Start/resume tracking changes + * @return {this} + * @example + * um.start(); */ start() { um.startTracking(); + return this; }, /** * Stop tracking changes + * @return {this} + * @example + * um.stop(); */ stop() { um.stopTracking(); + return this; }, /** * Undo last change + * @return {this} + * @example + * um.undo(); */ undo() { - if (em.get('Canvas').isInputFocused()) return; - um.undo(1); + if (!em.get('Canvas').isInputFocused()) um.undo(1); + return this; }, /** * Undo all changes + * @return {this} + * @example + * um.undoAll(); */ undoAll() { um.undoAll(); + return this; }, /** * Redo last change + * @return {this} + * @example + * um.redo(); */ redo() { - if (em.get('Canvas').isInputFocused()) return; - um.redo(1); + if (!em.get('Canvas').isInputFocused()) um.redo(1); + return this; }, /** * Redo all changes + * @return {this} + * @example + * um.redoAll(); */ redoAll() { um.redoAll(); + return this; }, /** - * Checks if there is an available undo + * Checks if exists an available undo * @return {Boolean} + * @example + * um.hasUndo(); */ hasUndo() { return um.isAvailable('undo'); @@ -162,8 +202,10 @@ module.exports = () => { /** - * Checks if there is an available redo + * Checks if exists an available redo * @return {Boolean} + * @example + * um.hasRedo(); */ hasRedo() { return um.isAvailable('redo'); @@ -172,7 +214,10 @@ module.exports = () => { /** * Get stack of changes - * @return {Array} + * @return {Collection} + * @example + * const stack = um.getStack(); + * stack.each(item => ...); */ getStack() { return um.stack; @@ -180,9 +225,13 @@ module.exports = () => { /** * Clear the stack + * @return {this} + * @example + * um.clear(); */ clear() { um.clear(); + return this; } }; };