UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering.

611 lines 23.8 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HistoryManager = void 0; var util_1 = require("../util"); var common_1 = require("../common"); var model_1 = require("../model/model"); var HistoryManager = /** @class */ (function (_super) { __extends(HistoryManager, _super); function HistoryManager(options) { var _this = _super.call(this) || this; _this.batchCommands = null; _this.batchLevel = 0; _this.lastBatchIndex = -1; _this.freezed = false; _this.handlers = []; _this.graph = options.graph; _this.model = options.graph.model; _this.options = Util.getOptions(options); _this.validator = new HistoryManager.Validator({ history: _this, cancelInvalid: _this.options.cancelInvalid, }); _this.clean(); _this.startListening(); return _this; } Object.defineProperty(HistoryManager.prototype, "disabled", { get: function () { return this.options.enabled !== true; }, enumerable: false, configurable: true }); HistoryManager.prototype.enable = function () { if (this.disabled) { this.options.enabled = true; } }; HistoryManager.prototype.disable = function () { if (!this.disabled) { this.options.enabled = false; } }; HistoryManager.prototype.undo = function (options) { if (options === void 0) { options = {}; } if (!this.disabled) { var cmd = this.undoStack.pop(); if (cmd) { this.revertCommand(cmd, options); this.redoStack.push(cmd); this.notify('undo', cmd, options); } } return this; }; HistoryManager.prototype.redo = function (options) { if (options === void 0) { options = {}; } if (!this.disabled) { var cmd = this.redoStack.pop(); if (cmd) { this.applyCommand(cmd, options); this.undoStack.push(cmd); this.notify('redo', cmd, options); } } return this; }; /** * Same as `undo()` but does not store the undo-ed command to the * `redoStack`. Canceled command therefore cannot be redo-ed. */ HistoryManager.prototype.cancel = function (options) { if (options === void 0) { options = {}; } if (!this.disabled) { var cmd = this.undoStack.pop(); if (cmd) { this.revertCommand(cmd, options); this.redoStack = []; this.notify('cancel', cmd, options); } } return this; }; HistoryManager.prototype.clean = function (options) { if (options === void 0) { options = {}; } this.undoStack = []; this.redoStack = []; this.notify('clean', null, options); return this; }; HistoryManager.prototype.canUndo = function () { return !this.disabled && this.undoStack.length > 0; }; HistoryManager.prototype.canRedo = function () { return !this.disabled && this.redoStack.length > 0; }; HistoryManager.prototype.validate = function (events) { var _a; var callbacks = []; for (var _i = 1; _i < arguments.length; _i++) { callbacks[_i - 1] = arguments[_i]; } (_a = this.validator).validate.apply(_a, __spreadArray([events], callbacks, false)); return this; }; HistoryManager.prototype.dispose = function () { this.validator.dispose(); this.clean(); this.stopListening(); }; HistoryManager.prototype.startListening = function () { var _this = this; this.model.on('batch:start', this.initBatchCommand, this); this.model.on('batch:stop', this.storeBatchCommand, this); if (this.options.eventNames) { this.options.eventNames.forEach(function (name, index) { _this.handlers[index] = _this.addCommand.bind(_this, name); _this.model.on(name, _this.handlers[index]); }); } this.validator.on('invalid', function (args) { return _this.trigger('invalid', args); }); }; HistoryManager.prototype.stopListening = function () { var _this = this; this.model.off('batch:start', this.initBatchCommand, this); this.model.off('batch:stop', this.storeBatchCommand, this); if (this.options.eventNames) { this.options.eventNames.forEach(function (name, index) { _this.model.off(name, _this.handlers[index]); }); this.handlers.length = 0; } this.validator.off('invalid'); }; HistoryManager.prototype.createCommand = function (options) { return { batch: options ? options.batch : false, data: {}, }; }; HistoryManager.prototype.revertCommand = function (cmd, options) { this.freezed = true; var cmds = Array.isArray(cmd) ? Util.sortBatchCommands(cmd) : [cmd]; for (var i = cmds.length - 1; i >= 0; i -= 1) { var cmd_1 = cmds[i]; var localOptions = __assign(__assign({}, options), util_1.ObjectExt.pick(cmd_1.options, this.options.revertOptionsList || [])); this.executeCommand(cmd_1, true, localOptions); } this.freezed = false; }; HistoryManager.prototype.applyCommand = function (cmd, options) { this.freezed = true; var cmds = Array.isArray(cmd) ? Util.sortBatchCommands(cmd) : [cmd]; for (var i = 0; i < cmds.length; i += 1) { var cmd_2 = cmds[i]; var localOptions = __assign(__assign({}, options), util_1.ObjectExt.pick(cmd_2.options, this.options.applyOptionsList || [])); this.executeCommand(cmd_2, false, localOptions); } this.freezed = false; }; HistoryManager.prototype.executeCommand = function (cmd, revert, options) { var model = this.model; // const cell = cmd.modelChange ? model : model.getCell(cmd.data.id!) var cell = model.getCell(cmd.data.id); var event = cmd.event; if ((Util.isAddEvent(event) && revert) || (Util.isRemoveEvent(event) && !revert)) { cell.remove(options); } else if ((Util.isAddEvent(event) && !revert) || (Util.isRemoveEvent(event) && revert)) { var data = cmd.data; if (data.node) { model.addNode(data.props, options); } else if (data.edge) { model.addEdge(data.props, options); } } else if (Util.isChangeEvent(event)) { var data = cmd.data; var key = data.key; if (key) { var value = revert ? data.prev[key] : data.next[key]; cell.prop(key, value, options); } } else { var executeCommand = this.options.executeCommand; if (executeCommand) { util_1.FunctionExt.call(executeCommand, this, cmd, revert, options); } } }; HistoryManager.prototype.addCommand = function (event, args) { if (this.freezed || this.disabled) { return; } var eventArgs = args; var options = eventArgs.options || {}; if (options.dryrun) { return; } if ((Util.isAddEvent(event) && this.options.ignoreAdd) || (Util.isRemoveEvent(event) && this.options.ignoreRemove) || (Util.isChangeEvent(event) && this.options.ignoreChange)) { return; } // before // ------ var before = this.options.beforeAddCommand; if (before != null && util_1.FunctionExt.call(before, this, event, args) === false) { return; } if (event === 'cell:change:*') { // eslint-disable-next-line event = "cell:change:" + eventArgs.key; } var cell = eventArgs.cell; var isModelChange = model_1.Model.isModel(cell); var cmd; if (this.batchCommands) { // In most cases we are working with same object, doing // same action etc. translate an object piece by piece. cmd = this.batchCommands[Math.max(this.lastBatchIndex, 0)]; // Check if we are start working with new object or performing different // action with it. Note, that command is uninitialized when lastCmdIndex // equals -1. In that case we are done, command we were looking for is // already set var diffId = (isModelChange && !cmd.modelChange) || cmd.data.id !== cell.id; var diffName = cmd.event !== event; if (this.lastBatchIndex >= 0 && (diffId || diffName)) { // Trying to find command first, which was performing same // action with the object as we are doing now with cell. var index = this.batchCommands.findIndex(function (cmd) { return ((isModelChange && cmd.modelChange) || cmd.data.id === cell.id) && cmd.event === event; }); if (index < 0 || Util.isAddEvent(event) || Util.isRemoveEvent(event)) { cmd = this.createCommand({ batch: true }); } else { cmd = this.batchCommands[index]; this.batchCommands.splice(index, 1); } this.batchCommands.push(cmd); this.lastBatchIndex = this.batchCommands.length - 1; } } else { cmd = this.createCommand({ batch: false }); } // add & remove // ------------ if (Util.isAddEvent(event) || Util.isRemoveEvent(event)) { var data = cmd.data; cmd.event = event; cmd.options = options; data.id = cell.id; data.props = util_1.ObjectExt.cloneDeep(cell.toJSON()); if (cell.isEdge()) { data.edge = true; } else if (cell.isNode()) { data.node = true; } return this.push(cmd, options); } // change:* // -------- if (Util.isChangeEvent(event)) { var key = args.key; var data = cmd.data; if (!cmd.batch || !cmd.event) { // Do this only once. Set previous data and action (also // serves as a flag so that we don't repeat this branche). cmd.event = event; cmd.options = options; data.key = key; if (data.prev == null) { data.prev = {}; } data.prev[key] = util_1.ObjectExt.clone(cell.previous(key)); if (isModelChange) { cmd.modelChange = true; } else { data.id = cell.id; } } if (data.next == null) { data.next = {}; } data.next[key] = util_1.ObjectExt.clone(cell.prop(key)); return this.push(cmd, options); } // others // ------ var afterAddCommand = this.options.afterAddCommand; if (afterAddCommand) { util_1.FunctionExt.call(afterAddCommand, this, event, args, cmd); } this.push(cmd, options); }; /** * Gather multiple changes into a single command. These commands could * be reverted with single `undo()` call. From the moment the function * is called every change made on model is not stored into the undoStack. * Changes are temporarily kept until `storeBatchCommand()` is called. */ // eslint-disable-next-line HistoryManager.prototype.initBatchCommand = function (options) { if (this.freezed) { return; } if (this.batchCommands) { this.batchLevel += 1; } else { this.batchCommands = [this.createCommand({ batch: true })]; this.batchLevel = 0; this.lastBatchIndex = -1; } }; /** * Store changes temporarily kept in the undoStack. You have to call this * function as many times as `initBatchCommand()` been called. */ HistoryManager.prototype.storeBatchCommand = function (options) { if (this.freezed) { return; } if (this.batchCommands && this.batchLevel <= 0) { var cmds = this.filterBatchCommand(this.batchCommands); if (cmds.length > 0) { this.redoStack = []; this.undoStack.push(cmds); this.notify('add', cmds, options); } this.batchCommands = null; this.lastBatchIndex = -1; this.batchLevel = 0; } else if (this.batchCommands && this.batchLevel > 0) { this.batchLevel -= 1; } }; HistoryManager.prototype.filterBatchCommand = function (batchCommands) { var cmds = batchCommands.slice(); var result = []; var _loop_1 = function () { var cmd = cmds.shift(); var evt = cmd.event; var id = cmd.data.id; if (evt != null && (id != null || cmd.modelChange)) { if (Util.isAddEvent(evt)) { var index_1 = cmds.findIndex(function (c) { return Util.isRemoveEvent(c.event) && c.data.id === id; }); if (index_1 >= 0) { cmds = cmds.filter(function (c, i) { return index_1 < i || c.data.id !== id; }); return "continue"; } } else if (Util.isRemoveEvent(evt)) { var index = cmds.findIndex(function (c) { return Util.isAddEvent(c.event) && c.data.id === id; }); if (index >= 0) { cmds.splice(index, 1); return "continue"; } } else if (Util.isChangeEvent(evt)) { var data = cmd.data; if (util_1.ObjectExt.isEqual(data.prev, data.next)) { return "continue"; } } else { // pass } result.push(cmd); } }; while (cmds.length > 0) { _loop_1(); } return result; }; HistoryManager.prototype.notify = function (event, cmd, options) { var cmds = cmd == null ? null : Array.isArray(cmd) ? cmd : [cmd]; this.emit(event, { cmds: cmds, options: options }); this.emit('change', { cmds: cmds, options: options }); }; HistoryManager.prototype.push = function (cmd, options) { this.redoStack = []; if (cmd.batch) { this.lastBatchIndex = Math.max(this.lastBatchIndex, 0); this.emit('batch', { cmd: cmd, options: options }); } else { this.undoStack.push(cmd); this.notify('add', cmd, options); } }; __decorate([ common_1.Basecoat.dispose() ], HistoryManager.prototype, "dispose", null); return HistoryManager; }(common_1.Basecoat)); exports.HistoryManager = HistoryManager; (function (HistoryManager) { /** * Runs a set of callbacks to determine if a command is valid. This is * useful for checking if a certain action in your application does * lead to an invalid state of the graph. */ var Validator = /** @class */ (function (_super) { __extends(Validator, _super); function Validator(options) { var _this = _super.call(this) || this; _this.map = {}; _this.command = options.history; _this.cancelInvalid = options.cancelInvalid !== false; _this.command.on('add', _this.onCommandAdded, _this); return _this; } Validator.prototype.onCommandAdded = function (_a) { var _this = this; var cmds = _a.cmds; return Array.isArray(cmds) ? cmds.every(function (cmd) { return _this.isValidCommand(cmd); }) : this.isValidCommand(cmds); }; Validator.prototype.isValidCommand = function (cmd) { if (cmd.options && cmd.options.validation === false) { return true; } var callbacks = (cmd.event && this.map[cmd.event]) || []; var handoverErr = null; callbacks.forEach(function (routes) { var i = 0; var rollup = function (err) { var fn = routes[i]; i += 1; try { if (fn) { fn(err, cmd, rollup); } else { handoverErr = err; return; } } catch (err) { rollup(err); } }; rollup(handoverErr); }); if (handoverErr) { if (this.cancelInvalid) { this.command.cancel(); } this.emit('invalid', { err: handoverErr }); return false; } return true; }; Validator.prototype.validate = function (events) { var _this = this; var callbacks = []; for (var _i = 1; _i < arguments.length; _i++) { callbacks[_i - 1] = arguments[_i]; } var evts = Array.isArray(events) ? events : events.split(/\s+/); callbacks.forEach(function (callback) { if (typeof callback !== 'function') { throw new Error(evts.join(' ') + " requires callback functions."); } }); evts.forEach(function (event) { if (_this.map[event] == null) { _this.map[event] = []; } _this.map[event].push(callbacks); }); return this; }; Validator.prototype.dispose = function () { this.command.off('add', this.onCommandAdded, this); }; __decorate([ common_1.Basecoat.dispose() ], Validator.prototype, "dispose", null); return Validator; }(common_1.Basecoat)); HistoryManager.Validator = Validator; })(HistoryManager = exports.HistoryManager || (exports.HistoryManager = {})); exports.HistoryManager = HistoryManager; var Util; (function (Util) { function isAddEvent(event) { return event === 'cell:added'; } Util.isAddEvent = isAddEvent; function isRemoveEvent(event) { return event === 'cell:removed'; } Util.isRemoveEvent = isRemoveEvent; function isChangeEvent(event) { return event != null && event.startsWith('cell:change:'); } Util.isChangeEvent = isChangeEvent; function getOptions(options) { var graph = options.graph, others = __rest(options, ["graph"]); var reservedNames = [ 'cell:added', 'cell:removed', 'cell:change:*', ]; var batchEvents = [ 'batch:start', 'batch:stop', ]; var eventNames = options.eventNames ? options.eventNames.filter(function (event) { return !(Util.isChangeEvent(event) || reservedNames.includes(event) || batchEvents.includes(event)); }) : reservedNames; return __assign(__assign({}, others), { eventNames: eventNames, applyOptionsList: options.applyOptionsList || ['propertyPath'], revertOptionsList: options.revertOptionsList || ['propertyPath'] }); } Util.getOptions = getOptions; function sortBatchCommands(cmds) { var results = []; for (var i = 0, ii = cmds.length; i < ii; i += 1) { var cmd = cmds[i]; var index = null; if (Util.isAddEvent(cmd.event)) { var id = cmd.data.id; for (var j = 0; j < i; j += 1) { if (cmds[j].data.id === id) { index = j; break; } } } if (index !== null) { results.splice(index, 0, cmd); } else { results.push(cmd); } } return results; } Util.sortBatchCommands = sortBatchCommands; })(Util || (Util = {})); //# sourceMappingURL=history.js.map