import Snap from 'snapsvg'
import ShapeResizer from './shape-resizer'
import { eventEmitter } from '../utils'
import SnapsvgZoom from './snapsvg-zoom'
/* custom plugins */
Snap.plugin(function (Snap, Element, Paper, glob) {
var elproto = Element.prototype;
elproto.toBack = function () {
this.prependTo(this.paper);
};
elproto.toFront = function () {
this.appendTo(this.paper);
};
});
Element.prototype.getTransformedXY = function( x, y ) {
var m = this.transform().globalMatrix;
return { x: m.x(x,y), y: m.y(x,y) };
};
var paper = null;
var mainImage = null;
var pointData = [];
var viewBoxBounds = {X: 100, Y:100};
var zoomFactor = {x:1, y:1};
var viewPort= {width:850, height:850};
var currentViewBox = [];
var config = null;
var readOnly = false;
var startPoint = null;
var drawing_path = null;
var canDraw = false;
var rectZone = null;
var PATH_COLOR = "#ff00ff";
var STROKE_COLOR = "red";
var FILL_COLOR = "orange";
var SELECTED_COLOR = "#ffff00";
var FIRST_NODE_COLOR = "#FF0000";
var HANDLE_SIZE = 8;
var isDragged = false;
var enablePoint = true;
var pathIsClosed = false;
var ENABLE_NEW_NODE = true;
var RECT_MODE ='RECT';
var drawingMode = RECT_MODE; //free
var FREE_MODE = 'FREE';
var availableModes = [RECT_MODE, FREE_MODE];
var onChangeCallback = null;
var getId = (function () {
var cpt = 0;
var defautPrefix = "item_";
return function (prefix) {
prefix = (typeof prefix === 'string') ? prefix : defautPrefix;
cpt = cpt + 1;
return prefix + cpt;
}
}());
var handleRectPath = function (path) {
if (readOnly) {
paper.path(path).attr({ stroke:'red', opacity: 0.6});
return;
}
var bbBox = Snap.path.getBBox(path);
rectZone = paper.rect(bbBox.x, bbBox.y, bbBox.width, bbBox.height);
rectZone.attr({fill: FILL_COLOR, stroke: STROKE_COLOR, opacity: 0.6});
drawing_path = rectZone;
canDraw = false;
pathIsClosed = true;
ShapeResizer.enable_resizer(paper, drawing_path, viewPort, currentViewBox);
};
var handleFreePath = function (path) {
if (readOnly) {
paper.path(path).attr({
stroke: 'orange',
fill: 'orange',
opacity: 0.5,
});
return;
}
var pathInfos = Snap.parsePathString(path);
pathInfos.map(function (pathData) {
if(pathData[0] !== 'Z') {
createPoint(paper, pathData[1], pathData[2], pointData);
} else {
pathIsClosed = true;
updatePath(paper, onClosePath);
}
});
/* replay the path here */
};
//transform point to path
var updatePath = function (paper, updateCallback) {
var path = "M";
if (pointData.length <= 1) {
return;
}
path += pointData[0].x + ',' + pointData[0].y;
for (var i=0; i < pointData.length; i++) {
if (i == 0) continue;
var pointInfos = pointData[i];
var lPath = "L" + pointInfos.x + "," + pointInfos.y;
path += " " + lPath;
}
path += (pathIsClosed) ? " Z": "";
/* remove prev path */
if (drawing_path) {
drawing_path.remove();
}
drawing_path = paper.path(path);
drawing_path.attr({
stroke: STROKE_COLOR,
"vector-effect": "non-scaling-stroke",//prevent line to be zoom in
"stroke-width": 3,
fill: "white",
opacity: 0.1
});
/* bring all handler to front */
pointData.map(function (point) {
/*deal with handler size */
var handleSize = computeHandleSize();
if (point.handler) {
point.handler.toFront();
}
});
if (typeof updateCallback === 'function' && pathIsClosed) {
updateCallback();
}
if (!updateCallback && pathIsClosed) {
applyClosedStyle();
}
};
var applyClosedStyle = function () {
drawing_path.attr({ fill: FILL_COLOR, strokeWidth: 1, opacity:.6 });
}
var onClosePath = function () {
ENABLE_NEW_NODE = false;
applyClosedStyle();
};
var onClickOnHandler = function (point, p, e) {
//close path
if (point.isFirst && pointData.length > 2) {
pathIsClosed = true;
}
};
var updatePointPosition = function (newPoint, x, y) {
var index = pointData.indexOf(newPoint);
if (index !== -1) {
pointData[index].x = x;
pointData[index].y = y;
return true;
} else {
return false;
}
};
var clearPreviousPath = function () {
drawing_path.remove();
};
var computeHandleSize = function () {
if(!currentViewBox.length) {
currentViewBox = [0, 0, parseInt(mainImage.width()), parseInt(mainImage.height())]
}
var currentHandleSize = HANDLE_SIZE * Math.min(currentViewBox[2], currentViewBox[3]) / 850;
return currentHandleSize;
}
var onMoveHandler = function (dx, dy, posX, posY, e) {
isDragged = true;
var tdx, tdy;
var snapInvMatrix = this.transform().diffMatrix.invert();
snapInvMatrix.e = snapInvMatrix.f = 0;
tdx = snapInvMatrix.x( dx,dy );
tdy = snapInvMatrix.y( dx,dy );
var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [tdx, tdy];
this.attr({ transform: transformValue});
var boxSize = this.getBBox();
var wasUpdated = updatePointPosition(this.data('point'), boxSize.x + (computeHandleSize() / 2) , boxSize.y + (computeHandleSize() / 2));
if (wasUpdated) {
updatePath(this.paper);
}
}
var bindHandlerEvent = function (point, p) {
point.handler.click(onClickOnHandler.bind(this, point, p));
/* -- handler -- */
point.handler.hover(function () {
point.handler.attr({fill: 'yellow'});
}, function () {
var fillColor = point.isFirst ? FIRST_NODE_COLOR : "";
point.handler.attr({fill: fillColor});
});
point.handler.drag(onMoveHandler, function () {
this.data('origTransform', this.transform().local );
}, function () {
if (!isDragged) { return true; }
isDragged = false;
enablePoint = false;
});
}
var createPointHandler = function (p, point) {
var handler;
var handleSize = computeHandleSize();
var handleX = point.x - handleSize / 2;
var handleY = point.y - handleSize / 2;
/* preserve initial size of 5px a quoi correspond 5 deal with current vp */
handler = p.rect(handleX, handleY, handleSize, handleSize);
handler.addClass("drawingHandler");
point.handler = handler;
point.handler.data('point', point);
if (pointData.length === 0) {
point.isFirst = true;
}
bindHandlerEvent(point, p);
point.handler.attr({
fill: (pointData.length === 0) ? FIRST_NODE_COLOR : "",
opacity: 0.9,
stroke: PATH_COLOR
});
return point;
}
//create paper
var createPoint = function (paper, x, y, pointData) {
var point = {x:x, y:y, id: getId()};
if (pathIsClosed) {
updatePath(paper, onClosePath);
return;
}
if (!enablePoint) {
enablePoint = true;
return false;
}
point = createPointHandler(paper, point);
pointData.push(point);
updatePath(paper);
};
var attachRectEvents = function (paper) {
if (readOnly) { return false; }
var startPosition = {};
var currentPosition = {};
/* add resizer */
paper.mousedown(function (e) {
if (drawingMode === FREE_MODE || pathIsClosed) { return; }
startPosition.x = e.offsetX;
startPosition.y = e.offsetY;
canDraw = true;
});
paper.mousemove(function (e) {
if (drawingMode === FREE_MODE) { return; }
if (!canDraw) { return; }
var x, y;
currentPosition.x = e.offsetX;
currentPosition.y = e.offsetY;
if (rectZone) {
rectZone.remove();
}
/* bas -> droite */
var width = Math.abs(currentPosition.x - startPosition.x);
var height = Math.abs(startPosition.y - currentPosition.y);
if (currentPosition.y > startPosition.y && currentPosition.x > startPosition.x) {
x = startPosition.x;
y = startPosition.y;
}
/* haut -> droite */
if (currentPosition.y < startPosition.y && currentPosition.x > startPosition.x) {
x = currentPosition.x - width;
y = currentPosition.y;
}
/* haut -> gauche */
if (currentPosition.y < startPosition.y && currentPosition.x < startPosition.x) {
x = currentPosition.x;
y = currentPosition.y;
}
/* bas -> gauche */
if (currentPosition.y > startPosition.y && currentPosition.x < startPosition.x) {
x = currentPosition.x
y = currentPosition.y - height;
}
if(!x || !y) { return; }
rectZone = paper.rect(x, y, width, height);
rectZone.attr({fill: FILL_COLOR, stroke: STROKE_COLOR, opacity: 0.6});
});
paper.mouseup(function () {
if ((drawingMode === FREE_MODE) || pathIsClosed || !rectZone) { return false; }
drawing_path = rectZone;
ShapeResizer.enable_resizer(paper, rectZone, viewPort, currentViewBox);
canDraw = false;
pathIsClosed = true;
});
};
var attachPointEvents = function (paper) {
if (readOnly) { return; }
paper.click( function(e) {
if (drawingMode === RECT_MODE) {
return true;
}
if (!ENABLE_NEW_NODE) { return true; }
createPoint(paper, e.offsetX, e.offsetY, pointData);
});
};
var attachZoomEvents = function () {
eventEmitter.on("zoomChanged", function (zoomInfos) {
zoomFactor = zoomInfos.zoomFactor;
currentViewBox = zoomInfos.currentViewBox;
var previousPath = API.getPath();
API.clear();
API.setPath(previousPath);
});
}
var API = {
getPaper: function () {
return paper;
},
setPath: function (pathString) {
/* redraw the path */
var pathInfos = pathString.split(';');
if ( availableModes.indexOf(pathInfos[1]) === -1) {
/* We assume then it is a free path */
pathInfos[1] = "FREE";
}
this.setDrawingMode(pathInfos[1]);
var pathData = pathInfos[0];
if (!pathData.length) {
return;
}
/* deal with path nomalization x = ImageWith/MaxXBound */
var xRatio = mainImage.attr("width") / viewBoxBounds.X;
var yRatio = mainImage.attr("height") / viewBoxBounds.Y;
if (isNaN(xRatio) || isNaN(yRatio)) {
new Error('Ratio should be a number.');
}
var transformMatrix = Snap.matrix(xRatio, 0, 0, yRatio, 0, 0);
var path = Snap.path.map(pathData, transformMatrix).toString();
/* always close path */
if (path.search(/[z|Z]/gi) === -1 ) {
path += "Z";
}
if (pathInfos.length >= 2) {
if (pathInfos[1] === RECT_MODE) {
handleRectPath(path);
}
if (pathInfos[1] === FREE_MODE) {
handleFreePath(path);
}
}
},
setDrawingMode: function (mode) {
if (availableModes.indexOf(mode) !== -1) {
drawingMode = mode;
}
if (typeof onChangeCallback === "function") {
onChangeCallback(drawingMode);
}
this.clear();
},
clear: function () {
/* clear previous path, point, handler */
pointData.map(function (point) {
if (point.handler) {
point.handler.remove();
}
});
/*clear path is exists*/
if (drawing_path) {
drawing_path.remove();
}
eventEmitter.emit("cutout:clear");
pointData = [];
startPoint = null;
drawing_path = null;
isDragged = false;
enablePoint = true;
pathIsClosed = false;
ENABLE_NEW_NODE = true;
},
getShapeBBox: function () {
var currentPath = this.getPath();
return Snap.path.getBBox(currentPath);
},
getShape: function () {
return this.getPath();
},
getPath: function () {
/* retourne le chemin */
/* send path and BBox | implement edit and load path */
var path = "";
if (drawing_path) {
if (drawingMode === RECT_MODE) {
var bBox = drawing_path.getBBox();
var transform = drawing_path.transform();
if (!transform.global.length) {
var shapePath = drawing_path.getBBox().path;
}
else {
var shapeX = drawing_path.node.getAttribute('x');
var shapeY = drawing_path.node.getAttribute('y');
var transformMatrix = transform.totalMatrix;
var fakeShape = paper.rect(transformMatrix.x(shapeX, shapeY),transformMatrix.y(shapeX, shapeY), bBox.width, bBox.height);
shapePath = fakeShape.getBBox().path;
fakeShape.remove();
}
path = Snap.path.toAbsolute(shapePath).toString();
}
else {
path = drawing_path.attr('d');
}
}
var xRatio = viewBoxBounds.X / mainImage.attr("width");
var yRatio = viewBoxBounds.Y / mainImage.attr("height");
if(isNaN(xRatio) || isNaN(yRatio)) {
new Error('ratio should be a number.');
}
if (!path.length) {
path = (drawingMode === RECT_MODE) ? ";RECT" : ";FREE";
return path;
}
var normalizeMatrix = Snap.matrix(xRatio, 0, 0, yRatio, 0, 0);
path = Snap.path.map(path, normalizeMatrix).toString();
/* save the type */
var type = (drawingMode === RECT_MODE) ? ";RECT" : ";FREE";
if (path.search(/[z|Z]/gi) === -1) {
path += " Z";
}
path += type;
return path;
}
};
/* change to a component */
export default {
init: function(config) {
mainImage = jQuery(config.wrapperId).find('.main-image').eq(0);
var cutCanvas = jQuery(config.wrapperId).find('.cut-canvas').eq(0);
var path = jQuery(config.wrapperId).find('.image-path').eq(0);
if (typeof config.onDrawingModeChange === 'function') {
onChangeCallback = config.onDrawingModeChange;
}
if (!mainImage.length) {
throw new Error("The main image Can't be found ...");
}
if (!cutCanvas.length) {
var cutCanvas = jQuery('<svg version="1.1"></svg>').addClass('cut-canvas');
jQuery(config.wrapperId).append(cutCanvas);
cutCanvas.append(mainImage);
}
cutCanvas.css({
marginLeft: 'auto',
marginRight: 'auto',
width: viewPort.width,
height: viewPort.height,
});
if (typeof config.readOnly === 'boolean' && config.readOnly === true) {
readOnly = true;
}
paper = new Snap(cutCanvas.get(0));
if (path.length) {
jQuery(cutCanvas).append(path);
var pathData = path.attr("d");
API.setPath(pathData);
path.remove();
}
/* enable zoom */
attachZoomEvents();
attachPointEvents(paper);
attachRectEvents(paper);
return API;
}
};