UNPKG

grapesjs-clot

Version:

Free and Open Source Web Builder Framework

449 lines (401 loc) 13.5 kB
/** * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/commands/config/config.js) * ```js * const editor = grapesjs.init({ * commands: { * // options * } * }) * ``` * * Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance. * * ```js * // Listen to events * editor.on('run', () => { ... }); * * // Use the API * const commands = editor.Commands; * commands.add(...); * ``` * ** ## Available Events * * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview')) * * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview')) * * `run:{commandName}:before` - Triggered before the command is called * * `stop:{commandName}:before` - Triggered before the command is called to stop * * `abort:{commandName}` - Triggered when the command execution is aborted (`editor.on(`run:preview:before`, opts => opts.abort = 1);`) * * `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback * * `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback * * ## Methods * * [add](#add) * * [get](#get) * * [getAll](#getall) * * [extend](#extend) * * [has](#has) * * [run](#run) * * [stop](#stop) * * [isActive](#isactive) * * [getActive](#getactive) * * @module Commands */ import { isFunction, includes } from 'underscore'; import CommandAbstract from './view/CommandAbstract'; import defaults from './config/config'; import { eventDrag } from 'dom_components/model/Component'; const commandsDef = [ ['preview', 'Preview', 'preview'], ['resize', 'Resize', 'resize'], ['fullscreen', 'Fullscreen', 'fullscreen'], ['copy', 'CopyComponent'], ['paste', 'PasteComponent'], ['canvas-move', 'CanvasMove'], ['canvas-clear', 'CanvasClear'], ['open-code', 'ExportTemplate', 'export-template'], ['open-layers', 'OpenLayers', 'open-layers'], ['open-styles', 'OpenStyleManager', 'open-sm'], ['open-traits', 'OpenTraitManager', 'open-tm'], ['open-blocks', 'OpenBlocks', 'open-blocks'], ['open-assets', 'OpenAssets', 'open-assets'], ['component-select', 'SelectComponent', 'select-comp'], ['component-outline', 'SwitchVisibility', 'sw-visibility'], ['component-offset', 'ShowOffset', 'show-offset'], ['component-move', 'MoveComponent', 'move-comp'], ['component-next', 'ComponentNext'], ['component-prev', 'ComponentPrev'], ['component-enter', 'ComponentEnter'], ['component-exit', 'ComponentExit', 'select-parent'], ['component-delete', 'ComponentDelete'], ['component-style-clear', 'ComponentStyleClear'], ['component-drag', 'ComponentDrag'], ]; export default () => { let em; let c = {}; const commands = {}; const defaultCommands = {}; const active = {}; // Need it here as it would be used below const add = function (id, obj) { if (isFunction(obj)) obj = { run: obj }; if (!obj.stop) obj.noStop = 1; delete obj.initialize; obj.id = id; commands[id] = CommandAbstract.extend(obj); return this; }; return { CommandAbstract, /** * Name of the module * @type {String} * @private */ name: 'Commands', /** * Initialize module. Automatically called with a new instance of the editor * @param {Object} config Configurations * @private */ init(config = {}) { c = { ...defaults, ...config, }; em = c.em; const ppfx = c.pStylePrefix; if (ppfx) c.stylePrefix = ppfx + c.stylePrefix; // Load commands passed via configuration Object.keys(c.defaults).forEach(k => { const obj = c.defaults[k]; if (obj.id) this.add(obj.id, obj); }); defaultCommands['tlb-delete'] = { run(ed) { return ed.runCommand('core:component-delete'); }, }; defaultCommands['tlb-clone'] = { run(ed) { ed.runCommand('core:copy'); ed.runCommand('core:paste', { action: 'clone-component' }); }, }; defaultCommands['tlb-move'] = { run(ed, sender, opts = {}) { let dragger; const em = ed.getModel(); const event = opts && opts.event; const { target } = opts; const sel = target || ed.getSelected(); const selAll = target ? [target] : [...ed.getSelectedAll()]; const nativeDrag = event && event.type == 'dragstart'; const defComOptions = { preserveSelected: 1 }; const modes = ['absolute', 'translate']; if (!sel || !sel.get('draggable')) { return em.logWarning('The element is not draggable'); } const mode = sel.get('dmode') || em.get('dmode'); const hideTlb = () => em.stopDefault(defComOptions); const altMode = includes(modes, mode); selAll.forEach(sel => sel.trigger('disable')); // Without setTimeout the ghost image disappears nativeDrag ? setTimeout(hideTlb, 0) : hideTlb(); const onStart = data => { em.trigger(`${eventDrag}:start`, data); }; const onDrag = data => { em.trigger(eventDrag, data); }; const onEnd = (e, opts, data) => { selAll.forEach(sel => sel.set('status', 'selected')); ed.select(selAll); sel.emitUpdate(); em.trigger(`${eventDrag}:end`, data); // Defer selectComponent in order to prevent canvas "freeze" #2692 setTimeout(() => em.runDefault(defComOptions)); // Dirty patch to prevent parent selection on drop (altMode || data.cancelled) && em.set('_cmpDrag', 1); }; if (altMode) { // TODO move grabbing func in editor/canvas from the Sorter dragger = ed.runCommand('core:component-drag', { guidesInfo: 1, mode, target: sel, onStart, onDrag, onEnd, event, }); } else { if (nativeDrag) { event.dataTransfer.setDragImage(sel.view.el, 0, 0); //sel.set('status', 'freezed'); } const cmdMove = ed.Commands.get('move-comp'); cmdMove.onStart = onStart; cmdMove.onDrag = onDrag; cmdMove.onEndMoveFromModel = onEnd; cmdMove.initSorterFromModels(selAll); } selAll.forEach(sel => sel.set('status', 'freezed-selected')); }, }; // Core commands defaultCommands['core:undo'] = e => e.UndoManager.undo(); defaultCommands['core:redo'] = e => e.UndoManager.redo(); commandsDef.forEach(item => { const oldCmd = item[2]; const cmd = require(`./view/${item[1]}`).default; const cmdName = `core:${item[0]}`; defaultCommands[cmdName] = cmd; if (oldCmd) { defaultCommands[oldCmd] = cmd; // Propogate old commands (can be removed once we stop to call old commands) ['run', 'stop'].forEach(name => { em.on(`${name}:${oldCmd}`, (...args) => em.trigger(`${name}:${cmdName}`, ...args)); }); } }); if (c.em) c.model = c.em.get('Canvas'); this.loadDefaultCommands(); return this; }, /** * Add new command to the collection * @param {string} id Command's ID * @param {Object|Function} command Object representing your command, * By passing just a function it's intended as a stateless command * (just like passing an object with only `run` method). * @return {this} * @example * commands.add('myCommand', { * run(editor, sender) { * alert('Hello world!'); * }, * stop(editor, sender) { * }, * }); * // As a function * commands.add('myCommand2', editor => { ... }); * */ add, /** * Get command by ID * @param {string} id Command's ID * @return {Object} Object representing the command * @example * var myCommand = commands.get('myCommand'); * myCommand.run(); * */ get(id) { let el = commands[id]; if (isFunction(el)) { el = new el(c); commands[id] = el; } else if (!el) { em.logWarning(`'${id}' command not found`); } return el; }, /** * Extend the command. The command to extend should be defined as an object * @param {string} id Command's ID * @param {Object} Object with the new command functions * @returns {this} * @example * commands.extend('old-command', { * someInnerFunction() { * // ... * } * }); * */ extend(id, cmd = {}) { const command = this.get(id); if (command) { const cmdObj = { ...command.constructor.prototype, ...cmd, }; this.add(id, cmdObj); // Extend also old name commands if exist const oldCmd = commandsDef.filter(cmd => `core:${cmd[0]}` === id && cmd[2])[0]; oldCmd && this.add(oldCmd[2], cmdObj); } return this; }, /** * Check if command exists * @param {string} id Command's ID * @return {Boolean} * */ has(id) { return !!commands[id]; }, /** * Get an object containing all the commands * @return {Object} */ getAll() { return commands; }, /** * Execute the command * @param {String} id Command ID * @param {Object} [options={}] Options * @return {*} The return is defined by the command * @example * commands.run('myCommand', { someOption: 1 }); */ run(id, options = {}) { return this.runCommand(this.get(id), options); }, /** * Stop the command * @param {String} id Command ID * @param {Object} [options={}] Options * @return {*} The return is defined by the command * @example * commands.stop('myCommand', { someOption: 1 }); */ stop(id, options = {}) { return this.stopCommand(this.get(id), options); }, /** * Check if the command is active. You activate commands with `run` * and disable them with `stop`. If the command was created without `stop` * method it can't be registered as active * @param {String} id Command id * @return {Boolean} * @example * const cId = 'some-command'; * commands.run(cId); * commands.isActive(cId); * // -> true * commands.stop(cId); * commands.isActive(cId); * // -> false */ isActive(id) { return this.getActive().hasOwnProperty(id); }, /** * Get all active commands * @return {Object} * @example * console.log(commands.getActive()); * // -> { someCommand: itsLastReturn, anotherOne: ... }; */ getActive() { return active; }, /** * Load default commands * @return {this} * @private * */ loadDefaultCommands() { for (var id in defaultCommands) { this.add(id, defaultCommands[id]); } return this; }, /** * Run command via its object * @param {Object} command * @param {Object} options * @return {*} Result of the command * @private */ runCommand(command, options = {}) { let result; if (command && command.run) { const id = command.id; const editor = em.get('Editor'); if (!this.isActive(id) || options.force || !c.strict) { result = editor && command.callRun(editor, options); if (id && command.stop && !command.noStop && !options.abort) { active[id] = result; } } } return result; }, /** * Stop the command * @param {Object} command * @param {Object} options * @return {*} Result of the command * @private */ stopCommand(command, options = {}) { let result; if (command && command.run) { const id = command.id; const editor = em.get('Editor'); if (this.isActive(id) || options.force || !c.strict) { if (id) delete active[id]; result = command.callStop(editor, options); } } return result; }, /** * Create anonymous Command instance * @param {Object} command Command object * @return {Command} * @private * */ create(command) { if (!command.stop) command.noStop = 1; const cmd = CommandAbstract.extend(command); return new cmd(c); }, destroy() { [em, c, commands, defaultCommands, active].forEach(i => (i = {})); }, }; };