drapcode-builder
Version:
Drapcode Builder Library
456 lines (409 loc) • 12.9 kB
JavaScript
/**
* 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. Before using these methods you should get the module from the instance
*
* ```js
* const commands = editor.Commands;
* ```
*
* * [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";
export default () => {
let em;
let c = {};
const commands = {};
const defaultCommands = {};
const active = {};
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"],
["save-db", "SaveDb", "save-db"],
["cmd-clear", "CanvasClear", "cmd-clear"],
["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"],
["set-device-desktop", "DeviceDesktop", "set-device-desktop"],
["set-device-tablet", "DeviceTablet", "set-device-tablet"],
["set-device-mobile", "DeviceMobile", "set-device-mobile"],
["set-device-mobile-ptr", "DeviceMobilePtr", "set-device-mobile-ptr"]
];
// 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
for (let k in c.defaults) {
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");
}
};
defaultCommands["tlb-trait-setting"] = {
run(ed, sender, opts = {}) {
const event = opts && opts.event;
const { target } = opts;
const sel = target || ed.getSelected();
const clientX = event.clientX;
const clientY = event.clientY;
const tm = ed.TraitManager;
const tmView = tm.getTraitsViewer();
ed.Popover.open({
title: "Settings",
content: tmView.render().el
})
.getModel()
.once("change:open");
}
};
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"];
const mode = sel.get("dmode") || em.get("dmode");
const hideTlb = () => em.stopDefault(defComOptions);
const altMode = includes(modes, mode);
selAll.forEach(sel => sel.trigger("disable"));
if (!sel || !sel.get("draggable")) {
return em.logWarning("The element is not draggable");
}
// 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) => {
em.runDefault(defComOptions);
selAll.forEach(sel => sel.set("status", "selected"));
ed.select(selAll);
sel.emitUpdate();
em.trigger(`${eventDrag}:end`, data);
// 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 id && 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 = 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 = editor && 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);
}
};
};