@wise-community/drawing-tool
Version:
HTML5 Drawing Tool
849 lines (762 loc) • 27.3 kB
JavaScript
/* global module require */
var $ = require('jquery');
var fabric = require('fabric').fabric;
var EventEmitter2 = require('eventemitter2');
var SelectionTool = require('./tools/select-tool');
var LineTool = require('./tools/shape-tools/line-tool');
var BasicShapeTool = require('./tools/shape-tools/basic-shape-tool');
var FreeDrawTool = require('./tools/shape-tools/free-draw');
var TextTool = require('./tools/shape-tools/text-tool');
var StampTool = require('./tools/shape-tools/stamp-tool');
var DeleteTool = require('./tools/delete-tool');
var CloneTool = require('./tools/clone-tool');
var AnnotationTool = require('./tools/shape-tools/annotation-tool');
var UIManager = require('./ui/ui-manager');
var UndoRedo = require('./undo-redo');
var convertState = require('./convert-state');
var rescale2resize = require('./fabric-extensions/rescale-2-resize');
var multitouchSupport = require('./fabric-extensions/multi-touch-support');
require('../styles/drawing-tool.scss');
var DEF_OPTIONS = {
width: 800,
height: 603,
// If this flag is set to true, stamp tool will try to parse SVG images
// using parser provided by FabricJS. It lets us avoid tainting canvas
// in some browsers which always do that when SVG image is rendered
// on canvas (e.g. Safari, IE).
// Also, when this option is set to false, it will case that bounding-box
// target find method is used instead of per-pixel one. It's cause by the fact
// that some browsers always taint canvas when SVG is rendered on it,
// (Safari, IE). Untainted canvas is necessary for per-pixel method.
parseSVG: true,
// if true, no events will be pushed to the undo/redo stack until `unpauseHistory`
// is explicitly called. This is useful for allowing public API methods to
// be called during initialization without dirtying the history
startWithHistoryPaused: false,
};
var DEF_STATE = {
stroke: '#3f3f3f',
fill: '',
strokeWidth: 8,
fontSize: 27
};
var EVENTS = {
// 'drawing:changed' is fired when the drawing (canvas) is updated by the user,
// for example new shape is added or existing one edited and so on.
DRAWING_CHANGED: 'drawing:changed',
// 'state:changed' is fired when the internal state of the drawing tool is updated,
// for example selected stroke color, fill color, font size and so on.
STATE_CHANGED: 'state:changed',
TOOL_CHANGED: 'tool:changed',
STAMP_CHANGED: 'stamp:changed',
UNDO_POSSIBLE: 'undo:possible',
UNDO_IMPOSSIBLE: 'undo:impossible',
REDO_POSSIBLE: 'redo:possible',
REDO_IMPOSSIBLE: 'redo:impossible'
};
// Note that some object properties aren't serialized by default by FabricJS.
// List them here so they can be serialized.
var ADDITIONAL_PROPS_TO_SERIALIZE = ['lockUniScaling', 'objectCaching'];
/**
* DrawingTool Constructor
* This does the work of initializing the entire webapp. It constructs the
* `DrawingTool` as well as the fabric.js canvas and UI.
*
* parameters:
* - selector: this is the selector for the div of where the DrawingTool will be housed
* - options: custom width and height for the drawTool canvas (see `DEF_OPTIONS` above)
* - settings: settings for starting state (see `DEF_STATE` above)
*/
function DrawingTool(selector, options, settings) {
this.selector = selector;
this.options = $.extend(true, {}, DEF_OPTIONS, options);
this.state = $.extend(true, {}, DEF_STATE, settings);
this._dispatch = new EventEmitter2({
wildcard: true,
newListener: false,
maxListeners: 100,
delimiter: ':'
});
this._initDOM();
this._initFabricJS();
this._setDimensions(this.options.width, this.options.height);
this._initStores();
this._initTools();
this._initStateHistory();
if (!this.canvasOnly()) {
new UIManager(this);
}
this.historyPaused = this.options.startWithHistoryPaused;
// Apply a fix that changes native FabricJS rescaling behavior into resizing.
rescale2resize(this.canvas);
// Adds support for multitouch support (pinching resize, two finger rotate, etc)
multitouchSupport(this.canvas);
// Note that at the beginning we will emmit two events - state:changed and tool:changed.
this._fireStateChanged();
this.chooseTool('select');
this.pushToHistory();
// Set background image without adding to state data.
if(this.options.backgroundImage) {
this._setBackgroundImage(this.options.backgroundImage);
}
// Listen for drawing changes if required
if (options.onDrawingChanged) {
this._dispatch.on(EVENTS.DRAWING_CHANGED, () => {
options.onDrawingChanged();
})
}
}
DrawingTool.prototype.ADDITIONAL_PROPS_TO_SERIALIZE = ADDITIONAL_PROPS_TO_SERIALIZE;
/**
* Proxy function that is used when images are loaded. Basic version just returns the same URL.
* Client code may provide custom function in DrawingTool options to return proxied url.
* E.g.:
* new DrawingTool({
* proxy: function (url) {
* return 'http://myproxy.com?url=' + url;
* }
* });
*/
DrawingTool.prototype.proxy = function (url) {
return (this.options.proxy && this.options.proxy(url)) || url;
};
/**
* Clears all objects from the fabric canvas and can also clear the background image
*
* parameters:
* - clearBackground: if true, this function will also remove the background image
*/
DrawingTool.prototype.clear = function (clearBackground) {
this.canvas.clear();
if (clearBackground) {
this.canvas.setBackgroundImage(null);
}
this.canvas.renderAll();
this.pushToHistory();
};
/**
* Deselects any selected objects and re-renders the fabricjs canvas
*/
DrawingTool.prototype.clearSelection = function () {
// Important! It will cause that all custom control points will be removed (e.g. for lines).
this.canvas.discardActiveObject();
this.canvas.renderAll();
};
/**
* Saves the current state of the fabricjs canvas into a JSON format.
* (used in conjunction with `load()`)
*/
DrawingTool.prototype.save = function () {
var selection = this.getSelection();
// There are two cases when we do want to remove selection before saving sate:
// 1. Custom control points are present. Obviously we don't want to serialize them.
// At the moment we assume that custom control points live only when
// the source object is selected and they are destroyed when selection is cleared.
// 2. There is a group selection (so selection is an array). Note that #toJSON method
// of canvas will discard group selection (and recreate it later) to ensure that all
// transformations applied to group will be applied to particular objects. However
// this happens without firing 'before:selection:cleared' event that is used by
// our custom rescale-2-resize behavior. So remove and recreate selection manually
// to and make sure that this even will be dispatched (#clearSelection does that).
var selectionCleared = false;
if (selection && (selection.hasCustomControlPoints || selection.length > 0)) {
this.clearSelection();
selectionCleared = true;
}
var canvasJSON = this.canvas.toJSON(ADDITIONAL_PROPS_TO_SERIALIZE);
// remove the annotation control points from the JSON output
if (fabric.Annotations) {
canvasJSON = fabric.Annotations.removeControlPointsFromJSON(canvasJSON);
}
var result = JSON.stringify({
version: 1,
dt: {
// Drawing Tool specific options.
width: this.canvas.getWidth(),
height: this.canvas.getHeight()
},
canvas: canvasJSON
});
if (selectionCleared) {
this.select(selection);
}
this.notifySave(result);
return result;
};
/*
* Loads a previous state of the fabricjs canvas from JSON.
* (used in conjunction with `save()`).
*
* parameters:
* - jsonString: JSON data, when it is not provided, canvas will be cleared
* - callback: function invoked when load is finished
* - noHistoryUpdate: if true, this action won't be saved in undo / redo history
*/
DrawingTool.prototype.load = function (jsonOrObject, callback, noHistoryUpdate) {
// When JSON string is not provided (or empty) just clear the canvas.
if (!jsonOrObject) {
this.canvas.clear();
this.canvas.setBackgroundImage(null);
this.canvas.renderAll();
loadFinished.call(this);
return;
}
var state = {};
if (typeof jsonOrObject === 'string' || jsonOrObject instanceof String){
state = JSON.parse(jsonOrObject);
}
else {
state = jsonOrObject;
}
state = convertState(state);
// Process Drawing Tool specific options.
var dtState = state.dt;
this._setDimensions(dtState.width, dtState.height);
// Load FabricJS state.
var loadDef = $.Deferred();
var canvasState = state.canvas;
if (fabric.Annotations) {
canvasState = fabric.Annotations.disableControlsInJSON(canvasState);
}
this.canvas.loadFromJSON(canvasState, loadDef.resolve.bind(loadDef));
$.when(loadDef).done(loadFinished.bind(this));
function loadFinished() {
// activate the select tool again to enable the annotation control points
this.tools.select.activateAgain();
// We don't serialize selectable property which depends on currently selected tool.
// Currently objects should be selectable only if select tool is active.
this.tools.select.setSelectable(this.tools.select.active);
if (!noHistoryUpdate) {
this.pushToHistory();
}
if (typeof callback === 'function') {
callback();
}
}
};
DrawingTool.prototype.canvasOnly = function () {
const buttons = this.options.buttons;
// Buttons are explicitly disabled. This means that Drawing Tool can be only used for presenting an existing drawing.
// It'll be rendered in the most basic basic way - no space for buttons panel and no styling (like canvas border).
return buttons !== undefined && (buttons === null || buttons.length === 0);
}
DrawingTool.prototype.pauseHistory = function () {
this.historyPaused = true;
}
DrawingTool.prototype.unpauseHistory = function () {
this.historyPaused = false;
}
DrawingTool.prototype.pushToHistory = function () {
if (!this.historyPaused) {
this._history.saveState();
this._fireHistoryEvents();
this._fireDrawingChanged();
}
};
DrawingTool.prototype.undo = function () {
this._history.undo(() => {
this._fireDrawingChanged();
});
this._fireHistoryEvents();
};
DrawingTool.prototype.redo = function () {
this._history.redo(() => {
this._fireDrawingChanged();
});
this._fireHistoryEvents();
};
DrawingTool.prototype.resetHistory = function () {
this._history.reset();
// Push the 'initial' state.
// We can't use public 'pushToHistory', 'drawing:changed' event shouldn't be emitted.
this._history.saveState();
this._fireHistoryEvents();
};
DrawingTool.prototype._fireHistoryEvents = function () {
if (this._history.canUndo()) {
this._dispatch.emit(EVENTS.UNDO_POSSIBLE);
} else {
this._dispatch.emit(EVENTS.UNDO_IMPOSSIBLE);
}
if (this._history.canRedo()) {
this._dispatch.emit(EVENTS.REDO_POSSIBLE);
} else {
this._dispatch.emit(EVENTS.REDO_IMPOSSIBLE);
}
};
DrawingTool.prototype._fireDrawingChanged = function () {
this._dispatch.emit(EVENTS.DRAWING_CHANGED);
};
/**
* Sets the stroke color for new shapes and fires a `stateEvent` to signal a
* change in the stroke color.
*
* parameters:
* - color: can be in any web-friendly format
* ex: literal-'black', hex-'#444444', or rgba-'rgba(100,200,200,.75)'
*/
DrawingTool.prototype.setStrokeColor = function (color) {
this.state.stroke = color;
this._fireStateChanged();
};
/**
* Sets the stroke width for new shapes and fires a `stateEvent` to signal a
* change in the stroke width.
*
* parameters:
* - width: integer for the desired width
*/
DrawingTool.prototype.setStrokeWidth = function (width) {
this.state.strokeWidth = width;
this._fireStateChanged();
};
/**
* Sets the font size for new text objects and fires a `stateEvent` to signal a
* change in the font size.
*
* parameters:
* - fontSize: integer for the desired font size
*/
DrawingTool.prototype.setFontSize = function (fontSize) {
this.state.fontSize = fontSize;
this._fireStateChanged();
};
/**
* Sets the fill color for new shapes and fires a `stateEvent` to signal a
* change in the fill color.
*
* parameters:
* - color: can be in any web-friendly format
* ex: literal-'black', hex-'#444444', or rgba-'rgba(100,200,200,.75)'
*/
DrawingTool.prototype.setFillColor = function (color) {
this.state.fill = color;
this._fireStateChanged();
};
DrawingTool.prototype.setSelectionStrokeColor = function (color) {
if (!this.getSelection()) return;
this.forEachSelectedObject(function (obj) {
this._setObjectProp(obj, 'stroke', color);
}.bind(this));
this.canvas.renderAll();
this.pushToHistory();
};
DrawingTool.prototype.setSelectionFillColor = function (color) {
if (!this.getSelection()) return;
this.forEachSelectedObject(function (obj) {
this._setObjectProp(obj, 'fill', color);
}.bind(this));
this.canvas.renderAll();
this.pushToHistory();
};
DrawingTool.prototype.setSelectionStrokeWidth = function (width) {
if (!this.getSelection()) return;
this.forEachSelectedObject(function (obj) {
this._setObjectProp(obj, 'strokeWidth', width);
}.bind(this));
this.canvas.renderAll();
this.pushToHistory();
};
DrawingTool.prototype.setSelectionFontSize = function (fontSize) {
if (!this.getSelection()) return;
this.forEachSelectedObject(function (obj) {
if (obj.type === 'i-text') {
this._setObjectProp(obj, 'fontSize', fontSize);
}
}.bind(this));
this.canvas.renderAll();
this.pushToHistory();
};
DrawingTool.prototype.sendSelectionToFront = function () {
if (!this.getSelection()) return;
this._sendSelectionTo('front');
this.pushToHistory();
};
DrawingTool.prototype.sendSelectionToBack = function () {
if (!this.getSelection()) return;
this._sendSelectionTo('back');
this.pushToHistory();
};
DrawingTool.prototype.forEachSelectedObject = function (callback) {
this.canvas.getActiveObjects().forEach(callback);
};
DrawingTool.prototype._setObjectProp = function (object, type, value) {
if (object.type === 'i-text') {
// Special case for text. We assume that text color is defined by 'stroke', not fill.
if (type === 'stroke') {
type = 'fill';
} else if (type === 'fill') {
return;
} else if (type === 'strokeWidth') {
return;
}
}
object.set(type, value);
};
DrawingTool.prototype._sendSelectionTo = function (where) {
var objects = this.canvas.getActiveObjects();
objects.forEach(send);
function send(obj) {
// Note that this function handles custom control points defined for lines.
// See: line-custom-control-points.js
if (obj._dt_sourceObj) {
send(obj._dt_sourceObj);
return;
}
if (where === 'front') {
obj.bringToFront();
// Make sure that custom control point are send to front AFTER shape itself.
if (obj._dt_controlPoints) {
obj._dt_controlPoints.forEach(function (cp) {
cp.bringToFront();
});
}
} else {
// Make sure that custom control point are send to back BEFORE shape itself.
if (obj._dt_controlPoints) {
obj._dt_controlPoints.forEach(function (cp) {
cp.sendToBack();
});
}
obj.sendToBack();
}
}
};
/**
* Set the background image for the fabricjs canvas.
*
* parameters:
* - imageSrcOrOptions: either -
* (a) string with location of the image
* (b) options object, including `src`
* - fit: (string) how to put the image into the canvas
* ex: 'resizeBackgroundToCanvas' or 'resizeCanvasToBackground'
* - callback: function which is called when background image is loaded and set.
*/
DrawingTool.prototype.setBackgroundImage = function (imageSrcOrOptions, fit, callback) {
var imageSrc = typeof imageSrcOrOptions === "string" ? imageSrcOrOptions : imageSrcOrOptions.src;
var imageOptions = typeof imageSrcOrOptions === "object" ? imageSrcOrOptions : null;
this._setBackgroundImage(imageSrc, imageOptions, function () {
switch (fit) {
case 'resizeBackgroundToCanvas': this.resizeBackgroundToCanvas(); break;
case 'resizeCanvasToBackground': this.resizeCanvasToBackground(); break;
case 'shrinkBackgroundToCanvas': this.shrinkBackgroundToCanvas(); break;
}
this.pushToHistory();
if (typeof callback === 'function') {
callback();
}
}.bind(this));
};
DrawingTool.prototype.resizeBackgroundToCanvas = function () {
if (!this.canvas.backgroundImage) {
return;
}
var bgImg = this.canvas.backgroundImage;
bgImg.set({
scaleX: this.canvas.width / bgImg.width,
scaleY: this.canvas.height / bgImg.height
});
this.canvas.renderAll();
this.pushToHistory();
};
// Fits background to canvas (keeping original aspect ratio) only when background is bigger than canvas.
DrawingTool.prototype.shrinkBackgroundToCanvas = function () {
if (!this.canvas.backgroundImage) {
return;
}
var bgImg = this.canvas.backgroundImage;
var widthRatio = this.canvas.width / bgImg.width;
var heightRatio = this.canvas.height / bgImg.height;
var minRatio = Math.min(widthRatio, heightRatio);
if (minRatio < 1) {
bgImg.set({ scaleX: minRatio, scaleY: minRatio });
this.canvas.renderAll();
this.pushToHistory();
}
};
DrawingTool.prototype.resizeCanvasToBackground = function () {
if (!this.canvas.backgroundImage) {
return;
}
var bgImg = this.canvas.backgroundImage;
this._setDimensions(bgImg.width, bgImg.height);
bgImg.set({
scaleX: 1,
scaleY: 1
});
if (bgImg.originX === "center") {
// If origin was set to top-left, it means that top and left are equal to 0 and there's nothing to update.
bgImg.set({
top: this.canvas.height / 2,
left: this.canvas.width / 2
});
}
this.canvas.renderAll();
this.pushToHistory();
};
DrawingTool.prototype.setDimensions = function (width, height) {
this._setDimensions(width, height);
this.pushToHistory();
};
/**
* Calculates canvas element offset relative to the document.
* Call this method when Drawing Tool container position is updated.
* This method is attached as 'resize' event handler of window (by FabricJS itself).
*/
DrawingTool.prototype.calcOffset = function () {
this.canvas.calcOffset();
};
/**
* Changes the current tool.
*
* parameters:
* - toolSelector: selector for the tool as sepecified in the contruction of the tool
*/
DrawingTool.prototype.chooseTool = function (toolSelector) {
var newTool = this.tools[toolSelector];
if (!newTool) {
return;
}
if (this.currentTool === newTool) {
// Some tools may implement .activateAgain() method and
// enable some special behavior.
this.currentTool.activateAgain();
return;
}
if (newTool.singleUse === true) {
// special single use tools should not be set as the current tool
newTool.use();
return;
}
// activate and deactivate the new and old tools
if (this.currentTool !== undefined) {
this.currentTool.setActive(false);
}
this.currentTool = newTool;
this.currentTool.setActive(true);
this._dispatch.emit(EVENTS.TOOL_CHANGED, toolSelector);
this.canvas.renderAll();
};
/**
* Changing the current tool out of this current tool to the default tool
* aka 'select' tool
*/
DrawingTool.prototype.changeOutOfTool = function () {
this.chooseTool('select');
};
DrawingTool.prototype.on = function () {
this._dispatch.on.apply(this._dispatch, arguments);
};
// off (name, handler)
DrawingTool.prototype.off = function () {
this._dispatch.off.apply(this._dispatch, arguments);
};
/**
* Selects passed object or array of objects.
*/
DrawingTool.prototype.select = function (objectOrObjects) {
this.clearSelection();
if (!objectOrObjects) {
return;
}
if (objectOrObjects.length === 1) {
objectOrObjects = objectOrObjects[0];
}
if (!objectOrObjects.length) {
// Simple scenario, select a single object.
this.canvas.setActiveObject(objectOrObjects);
return;
}
// More complex case, create a group and select it.
var selection = new fabric.ActiveSelection(objectOrObjects, {
originX: 'center',
originY: 'center',
canvas: this.canvas
});
this.canvas.setActiveObject(selection);
// Important! E.g. ensures that outlines around objects are visible.
this.canvas.requestRenderAll();
};
/**
* Returns selected object or array of selected objects.
*/
DrawingTool.prototype.getSelection = function () {
var actGroup = this.canvas.getActiveObjects();
if (actGroup.length > 1) {
return actGroup;
} else if (actGroup.length === 1) {
var actObject = actGroup[0];
return actObject.isControlPoint ? actObject._dt_sourceObj : actObject;
}
};
DrawingTool.prototype._fireStateChanged = function () {
this._dispatch.emit(EVENTS.STATE_CHANGED, this.state);
};
/**
* * parameters:
* - _options: Either an object specifying how the background is to be placed and loaded, or
* an object with the property "position" that is one of two options:
* "center" (default) or "top-left".
*/
DrawingTool.prototype._setBackgroundImage = function (imageSrc, _options, backgroundLoadedCallback) {
var options;
if (typeof _opions === "object" && !_opions.position) {
options = _options;
} else if (_options && _options.position === "top-left") {
options = {
originX: 'left',
originY: 'top',
top: 0,
left: 0,
crossOrigin: 'anonymous'
};
} else {
options = {
originX: 'center',
originY: 'center',
top: this.canvas.height / 2,
left: this.canvas.width / 2,
crossOrigin: 'anonymous'
};
}
var self = this;
if (!imageSrc) {
// Fast path when we remove background image.
this.canvas.setBackgroundImage(null, bgLoaded);
} else {
imageSrc = this.proxy(imageSrc);
loadImage();
}
function loadImage() {
// Note we cannot use fabric.Image.fromURL, as then we would always get
// fabric.Image instance and we couldn't guess whether load failed or not.
// util.loadImage provides null to callback when loading fails.
fabric.util.loadImage(imageSrc, callback, null, options.crossOrigin);
}
function callback (img) {
// If image is null and crossOrigin settings are available, it probably means that loading failed
// due to lack of CORS headers. Try again without them.
if ((options.crossOrigin === 'anonymous' || options.crossOrigin === '') && !img) {
options = $.extend(true, {}, options);
delete options.crossOrigin;
console.log('Background could not be loaded due to lack of CORS headers. Trying to load it again without CORS support.');
loadImage();
return;
}
self.canvas.setBackgroundImage(new fabric.Image(img, options), bgLoaded);
}
function bgLoaded() {
if (typeof backgroundLoadedCallback === 'function') {
backgroundLoadedCallback();
}
self.canvas.renderAll();
}
};
DrawingTool.prototype._initTools = function () {
// Initialize all the tools, they add themselves to the tools hash.
this.tools = {
select: new SelectionTool('Selection Tool', this),
line: new LineTool('Line Tool', this),
arrow: new LineTool('Arrow Tool', this, 'arrow'),
doubleArrow: new LineTool('Double Arrow Tool', this, 'arrow', {doubleArrowhead: true}),
rect: new BasicShapeTool('Rectangle Tool', this, 'rect'),
ellipse: new BasicShapeTool('Ellipse Tool', this, 'ellipse'),
square: new BasicShapeTool('Square Tool', this, 'square'),
circle: new BasicShapeTool('Circle Tool', this, 'circle'),
free: new FreeDrawTool('Free Draw Tool', this),
stamp: new StampTool('Stamp Tool', this, this.options.parseSVG),
text: new TextTool('Text Tool', this),
trash: new DeleteTool('Delete Tool', this),
clone: new CloneTool('Clone Tool', this),
annotation: new AnnotationTool('Annotation Tool', this)
};
};
DrawingTool.prototype._initDOM = function () {
$(this.selector).empty();
this.$element = $('<div class="dt-container">')
.appendTo(this.selector);
var $canvasContainer = $('<div class="dt-canvas-container">')
.attr('tabindex', 0) // makes the canvas focusable for keyboard events
.appendTo(this.$element);
if (!this.canvasOnly()) {
$canvasContainer.addClass("with-border");
}
if (this.options.canvasScale) {
$canvasContainer.css({
"transform-origin": "top left",
"transform": `scale(${this.options.canvasScale})`,
})
}
this.$canvas = $('<canvas>')
.appendTo($canvasContainer);
};
DrawingTool.prototype._initFabricJS = function () {
this.canvas = new fabric.Canvas(this.$canvas[0], { preserveObjectStacking: true });
this.canvas.targetFindTolerance = 12;
// Target find would be more tolerant on touch devices.
// Also SVG images added to canvas will taint it in some browsers, no matter whether
// it's coming from the same or another domain (e.g. Safari, IE). In such case, we
// have to use bounding box target find, as per pixel tries to read canvas data
// (impossible when canvas is tainted).
this.canvas._isPerPixelTargetFindAllowed = !fabric.isTouchSupported && this.options.parseSVG;
this.canvas.setBackgroundColor('#fff');
};
DrawingTool.prototype._setDimensions = function (width, height) {
this.canvas.setDimensions({
width: width,
height: height
});
// devicePixelRatio may be undefined in old browsers.
var pixelRatio = window.devicePixelRatio || 1;
if (pixelRatio !== 1) {
var canvEl = this.canvas.getElement();
$(canvEl)
.attr('width', width * pixelRatio)
.attr('height', height * pixelRatio)
.css('width', width)
.css('height', height);
canvEl.getContext('2d').scale(pixelRatio, pixelRatio);
}
};
DrawingTool.prototype._initStateHistory = function () {
this._history = new UndoRedo(this);
this.canvas.on('object:modified', function () {
this.pushToHistory();
}.bind(this));
};
DrawingTool.prototype._initStores = function() {
this.stores = [];
};
DrawingTool.prototype.addStore = function(storeImp) {
var loadFunction = this.load.bind(this);
var stores = this.stores;
if(stores.indexOf(storeImp) == -1) {
storeImp.setLoadFunction(loadFunction);
this.stores.push(storeImp);
}
};
DrawingTool.prototype.notifySave = function(serializedJson) {
var store = null;
var stores = this.stores || [];
for(var i=0; i < stores.length; i++) {
store = stores[i];
if(typeof store.save === 'function'){
try {
store.save(serializedJson);
}
catch (problem) {
console.error('unable to call `save(serializedJson)` on store');
console.error(problem);
}
}
else {
console.error('store does not implement required `save(serializedJson)` function!');
}
}
};
DrawingTool.prototype.setStampObject = function (stamp, imgSrc) {
this.tools.stamp.setStampObject(stamp);
this._dispatch.emit(EVENTS.STAMP_CHANGED, {stamp, imgSrc});
};
module.exports = DrawingTool;