Browse Source

Merge branch 'undo-manager' into dev

pull/663/head
Artur Arseniev 9 years ago
parent
commit
60d919d3b3
  1. 2
      src/block_manager/view/BlocksView.js
  2. 4
      src/commands/index.js
  3. 3
      src/commands/view/InsertCustom.js
  4. 22
      src/css_composer/index.js
  5. 43
      src/dom_components/index.js
  6. 6
      src/dom_components/view/ComponentTextView.js
  7. 6
      src/editor/config/config.js
  8. 3
      src/editor/index.js
  9. 194
      src/editor/model/Editor.js
  10. 3
      src/grapesjs/config/config.js
  11. 30
      src/keymaps/index.js
  12. 4
      src/style_manager/view/SectorsView.js
  13. 237
      src/undo_manager/index.js

2
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);
}
},

4
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();

3
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);
},

22
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.

43
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;
@ -237,9 +238,47 @@ 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.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);
um && comps && um.add(comps);
const evn = 'change:style change:content change:attributes change:src';
[ [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]);
em.listenTo(els[0], els[1], els[2]);
});
!opts.avoidStore && handleUpdates('', '', opts);
comps.each(model => this.handleChanges(model, value, opts));
},
/**
* 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

6
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');

6
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: {},

3
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
@ -571,6 +573,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);
});

194
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'),
@ -22,8 +23,6 @@ const deps = [
];
const Backbone = require('backbone');
const UndoManager = require('backbone-undo');
const key = require('keymaster');
let timedInterval;
require('utils/extender')({
@ -57,12 +56,7 @@ module.exports = Backbone.Model.extend({
this.config.components = c.el.innerHTML;
// Load modules
deps.forEach(function(name){
this.loadModule(name);
}, this);
this.initUndoManager();
deps.forEach(name => this.loadModule(name));
this.on('change:selectedComponent', this.componentSelected, this);
this.on('change:changesCount', this.updateChanges, this);
},
@ -94,16 +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 = () => {
// 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.get('modules').forEach(module =>
module.postLoad && module.postLoad(this)
);
const modules = this.get('modules');
modules.forEach(module => module.postLoad && module.postLoad(this));
clb && clb();
};
@ -151,22 +139,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;
},
@ -187,29 +173,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
@ -232,82 +195,6 @@ module.exports = Backbone.Model.extend({
}, 0);
},
/**
* Initialize Undo manager
* @private
* */
initUndoManager() {
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);
}
},
/**
* Callback on component selection
@ -325,67 +212,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);
},
/**
* 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) {
this.initChildrenComp(md);
if(this.um)
this.um.register(md);
}, this);
},
/**
* 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

3
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: {},

30
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

4
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) {

237
src/undo_manager/index.js

@ -0,0 +1,237 @@
/**
* This module allows to manage the stack of changes applied in canvas
*
* You can access the module in this way
* ```js
* const um = editor.UndoManager;
* ```
*
*/
import UndoManager from 'backbone-undo';
module.exports = () => {
let em;
let um;
let config;
let beforeCache;
const configDef = {};
return {
name: 'UndoManager',
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init(opts = {}) {
config = { ...opts, ...configDef };
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());
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);
},
redo(model, bf, af, opt) {
model.set(af);
}
};
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;
},
/**
* Get module configurations
* @return {Object} Configuration object
* @example
* const config = um.getConfig();
* // { ... }
*/
getConfig() {
return config;
},
/**
* 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();
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()) 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()) um.redo(1);
return this;
},
/**
* Redo all changes
* @return {this}
* @example
* um.redoAll();
*/
redoAll() {
um.redoAll();
return this;
},
/**
* Checks if exists an available undo
* @return {Boolean}
* @example
* um.hasUndo();
*/
hasUndo() {
return um.isAvailable('undo');
},
/**
* Checks if exists an available redo
* @return {Boolean}
* @example
* um.hasRedo();
*/
hasRedo() {
return um.isAvailable('redo');
},
/**
* Get stack of changes
* @return {Collection}
* @example
* const stack = um.getStack();
* stack.each(item => ...);
*/
getStack() {
return um.stack;
},
/**
* Clear the stack
* @return {this}
* @example
* um.clear();
*/
clear() {
um.clear();
return this;
}
};
};
Loading…
Cancel
Save