src_js/iconolab-bundle/src/components/cutout/svgboard.js
changeset 146 f912b591e1c1
child 156 e1e14766f608
equal deleted inserted replaced
145:de5736883786 146:f912b591e1c1
       
     1 
       
     2 
       
     3 import Snap from 'snapsvg'
       
     4 import ShapeResizer from './shape-resizer'
       
     5 import { eventEmitter } from '../utils'
       
     6 import SnapsvgZoom from './snapsvg-zoom'
       
     7 
       
     8 /* custom plugins */
       
     9 Snap.plugin(function (Snap, Element, Paper, glob) {
       
    10 	var elproto = Element.prototype;
       
    11 
       
    12 	elproto.toBack = function () {
       
    13 		this.prependTo(this.paper);
       
    14 	};
       
    15 
       
    16 	elproto.toFront = function () {
       
    17 		this.appendTo(this.paper);
       
    18 	};
       
    19 });
       
    20 
       
    21 Element.prototype.getTransformedXY = function( x, y ) {
       
    22             var m = this.transform().globalMatrix;
       
    23             return { x: m.x(x,y), y: m.y(x,y) };             
       
    24     };
       
    25 
       
    26 var paper = null;
       
    27 var mainImage = null;
       
    28 var pointData = [];
       
    29 var viewBoxBounds = {X: 100, Y:100};
       
    30 var zoomFactor = {x:1, y:1};
       
    31 var viewPort= {width:850, height:850};
       
    32 var currentViewBox = []; 
       
    33 var config = null;
       
    34 var readOnly = false;
       
    35 var startPoint = null;
       
    36 var drawing_path = null;
       
    37 var canDraw = false;
       
    38 var rectZone = null;
       
    39 var PATH_COLOR = "#ff00ff";
       
    40 var STROKE_COLOR = "red";
       
    41 var FILL_COLOR = "orange";
       
    42 
       
    43 var SELECTED_COLOR = "#ffff00";
       
    44 var FIRST_NODE_COLOR = "#FF0000";
       
    45 var HANDLE_SIZE = 8;
       
    46 var isDragged = false;
       
    47 var enablePoint = true;
       
    48 var pathIsClosed = false;
       
    49 var ENABLE_NEW_NODE = true;
       
    50 var RECT_MODE ='RECT';
       
    51 var drawingMode = RECT_MODE; //free
       
    52 var FREE_MODE = 'FREE';
       
    53 var availableModes = [RECT_MODE, FREE_MODE];
       
    54 var onChangeCallback = null;
       
    55 
       
    56 var getId = (function () {
       
    57 		var cpt = 0;
       
    58 		var defautPrefix = "item_"; 
       
    59 		return function (prefix) {
       
    60 			prefix = (typeof prefix === 'string') ? prefix : defautPrefix;
       
    61 			cpt = cpt + 1;
       
    62 			return prefix + cpt; 
       
    63 		}
       
    64 	}());
       
    65 
       
    66 var handleRectPath = function (path) {
       
    67 	if (readOnly) {
       
    68 		paper.path(path).attr({ stroke:'red', opacity: 0.6});
       
    69 		return;
       
    70 	}
       
    71 
       
    72 	var bbBox = Snap.path.getBBox(path);
       
    73 	rectZone = paper.rect(bbBox.x, bbBox.y, bbBox.width, bbBox.height);
       
    74 	rectZone.attr({fill: FILL_COLOR, stroke: STROKE_COLOR, opacity: 0.6});
       
    75 	drawing_path = rectZone;
       
    76 	canDraw = false;
       
    77 	pathIsClosed = true;
       
    78 	ShapeResizer.enable_resizer(paper, drawing_path, viewPort, currentViewBox);
       
    79 };
       
    80 
       
    81 
       
    82 var handleFreePath = function (path) {
       
    83 
       
    84 	if (readOnly) {
       
    85 		
       
    86 		paper.path(path).attr({
       
    87 			stroke: 'orange',
       
    88 			fill: 'orange',
       
    89 			opacity: 0.5,
       
    90 		});
       
    91 
       
    92 		return; 
       
    93 	}
       
    94 
       
    95 	var pathInfos = Snap.parsePathString(path);
       
    96 	pathInfos.map(function (pathData) {
       
    97 		if(pathData[0] !== 'Z') {
       
    98 			createPoint(paper, pathData[1], pathData[2], pointData);
       
    99 		} else {
       
   100 			pathIsClosed = true;
       
   101 			updatePath(paper, onClosePath);
       
   102 		}
       
   103 	});
       
   104 
       
   105 	/* replay the path here */
       
   106 
       
   107 }; 
       
   108 //transform point to path
       
   109 var updatePath = function (paper, updateCallback) {
       
   110 	var path = "M";
       
   111 
       
   112 	if (pointData.length <= 1) {
       
   113 		return;
       
   114 	}
       
   115 
       
   116 	path += pointData[0].x + ',' + pointData[0].y;
       
   117 
       
   118 	for (var i=0; i < pointData.length; i++) {
       
   119 		if (i == 0) continue;
       
   120 
       
   121 		var pointInfos = pointData[i];
       
   122 		var lPath = "L" + pointInfos.x + "," + pointInfos.y;
       
   123 		path += " " + lPath;
       
   124 	}
       
   125 	
       
   126 	path += (pathIsClosed) ? " Z": "";
       
   127 
       
   128 	/* remove prev path */
       
   129 	if (drawing_path) {
       
   130 		drawing_path.remove();
       
   131 	}	
       
   132 
       
   133 	drawing_path = paper.path(path);
       
   134 
       
   135 	drawing_path.attr({
       
   136 		stroke: STROKE_COLOR,
       
   137 		"vector-effect": "non-scaling-stroke",//prevent line to be zoom in
       
   138 		"stroke-width": 3,
       
   139 		fill: "white",
       
   140 		opacity: 0.1
       
   141 	});
       
   142 	
       
   143 	/* bring all handler to front */
       
   144 	pointData.map(function (point) {
       
   145 		
       
   146 		/*deal with handler size */
       
   147 		var handleSize = computeHandleSize();
       
   148 		if (point.handler) {
       
   149 			point.handler.toFront();
       
   150 		}
       
   151 	});
       
   152 
       
   153 	if (typeof updateCallback === 'function' && pathIsClosed) {
       
   154 		updateCallback();
       
   155 	}
       
   156 
       
   157 	if (!updateCallback && pathIsClosed) {
       
   158 		applyClosedStyle();	
       
   159 	}
       
   160 };
       
   161 
       
   162 var applyClosedStyle = function () {
       
   163 	drawing_path.attr({ fill: FILL_COLOR, strokeWidth: 1, opacity:.6 });
       
   164 } 
       
   165 
       
   166 var onClosePath = function () {
       
   167 	ENABLE_NEW_NODE = false;
       
   168 	applyClosedStyle();
       
   169 };
       
   170 
       
   171 
       
   172 var onClickOnHandler = function (point, p, e) {
       
   173 	//close path
       
   174 	if (point.isFirst && pointData.length > 2) {
       
   175 		pathIsClosed = true;
       
   176 	}
       
   177 };
       
   178 
       
   179 var updatePointPosition = function (newPoint, x, y) {
       
   180 	var index = pointData.indexOf(newPoint);
       
   181 	if (index !== -1) {
       
   182 		pointData[index].x = x;
       
   183 		pointData[index].y = y; 
       
   184 		return true;
       
   185 	} else {
       
   186 		return false;
       
   187 	}
       
   188 };
       
   189 
       
   190 var clearPreviousPath = function () {
       
   191 	drawing_path.remove();
       
   192 };
       
   193 
       
   194 var computeHandleSize = function () {
       
   195 
       
   196 	if(!currentViewBox.length) {
       
   197 		currentViewBox = [0, 0, parseInt(mainImage.width()), parseInt(mainImage.height())]
       
   198 	}
       
   199 	var currentHandleSize = HANDLE_SIZE * Math.min(currentViewBox[2], currentViewBox[3]) / 850; 
       
   200 	return currentHandleSize;
       
   201 }
       
   202  
       
   203 var onMoveHandler = function (dx, dy, posX, posY, e) {
       
   204 	isDragged = true;
       
   205 	var tdx, tdy;
       
   206     var snapInvMatrix = this.transform().diffMatrix.invert();
       
   207     snapInvMatrix.e = snapInvMatrix.f = 0;
       
   208     tdx = snapInvMatrix.x( dx,dy ); 
       
   209     tdy = snapInvMatrix.y( dx,dy );
       
   210 	var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [tdx, tdy];
       
   211 	this.attr({ transform: transformValue});
       
   212 	var boxSize = this.getBBox();
       
   213 
       
   214 	var wasUpdated = updatePointPosition(this.data('point'), boxSize.x + (computeHandleSize() / 2) , boxSize.y + (computeHandleSize() / 2));
       
   215 	
       
   216 	if (wasUpdated) {
       
   217 		updatePath(this.paper);
       
   218 	}
       
   219 }
       
   220 
       
   221 var bindHandlerEvent = function (point, p) {
       
   222 	point.handler.click(onClickOnHandler.bind(this, point, p));
       
   223 	/* -- handler -- */
       
   224 	point.handler.hover(function () {
       
   225 		point.handler.attr({fill: 'yellow'});
       
   226 	}, function () {
       
   227 		var fillColor = point.isFirst ? FIRST_NODE_COLOR : "";
       
   228 		point.handler.attr({fill: fillColor});
       
   229 	});
       
   230 	
       
   231 	point.handler.drag(onMoveHandler, function () {
       
   232         this.data('origTransform', this.transform().local );
       
   233 	}, function () {
       
   234 		if (!isDragged) { return true; }
       
   235 		isDragged = false;
       
   236 		enablePoint = false;
       
   237 	});
       
   238 }
       
   239 
       
   240 var createPointHandler = function (p, point) {
       
   241 	var handler;
       
   242 	var handleSize = computeHandleSize();
       
   243 	var handleX = point.x - handleSize / 2;
       
   244 	var handleY = point.y - handleSize / 2;
       
   245 
       
   246 	/* preserve initial size of 5px a quoi correspond 5 deal with current vp */
       
   247 	handler = p.rect(handleX, handleY, handleSize, handleSize);
       
   248 	
       
   249 	handler.addClass("drawingHandler");
       
   250 	point.handler = handler;
       
   251 	point.handler.data('point', point);
       
   252 	if (pointData.length === 0) {
       
   253 		point.isFirst = true;
       
   254 	}
       
   255 	
       
   256 	bindHandlerEvent(point, p);
       
   257 	point.handler.attr({
       
   258 		fill: (pointData.length === 0) ? FIRST_NODE_COLOR : "",
       
   259 		opacity: 0.9,
       
   260 		stroke: PATH_COLOR
       
   261 	});
       
   262 
       
   263 	return point;
       
   264 }
       
   265 
       
   266 //create paper
       
   267 var createPoint = function (paper, x, y, pointData) {
       
   268 
       
   269 	var point = {x:x, y:y, id: getId()};
       
   270 
       
   271 	if (pathIsClosed) {
       
   272 		updatePath(paper, onClosePath);
       
   273 		return;
       
   274 	}
       
   275 
       
   276 	if (!enablePoint) {
       
   277 		enablePoint = true;
       
   278 		return false;
       
   279 	}
       
   280 
       
   281 	point = createPointHandler(paper, point);
       
   282 	pointData.push(point);
       
   283 	updatePath(paper);
       
   284 };
       
   285 
       
   286 var attachRectEvents = function (paper) {
       
   287 	if (readOnly) { return false; }
       
   288 
       
   289 	var startPosition = {};
       
   290 	var currentPosition = {};
       
   291 	/* add resizer */
       
   292 
       
   293 	paper.mousedown(function (e) {
       
   294 
       
   295 		if (drawingMode === FREE_MODE || pathIsClosed) { return; }
       
   296 		startPosition.x = e.offsetX;
       
   297 		startPosition.y = e.offsetY;
       
   298 		canDraw = true;
       
   299 	});
       
   300 
       
   301 	paper.mousemove(function (e) {
       
   302 		if (drawingMode === FREE_MODE) { return; }
       
   303 		if (!canDraw) { return; }
       
   304 		var x, y;
       
   305 		currentPosition.x = e.offsetX;
       
   306 		currentPosition.y = e.offsetY;
       
   307 		
       
   308 		if (rectZone) {
       
   309 			rectZone.remove();
       
   310 		}
       
   311 
       
   312 		/* bas -> droite */
       
   313 		var width = Math.abs(currentPosition.x - startPosition.x);
       
   314 		var height = Math.abs(startPosition.y - currentPosition.y);
       
   315 
       
   316 		if (currentPosition.y > startPosition.y && currentPosition.x > startPosition.x) {
       
   317 			x = startPosition.x;
       
   318 			y = startPosition.y;	
       
   319 		}
       
   320 		
       
   321 		/* haut -> droite */
       
   322 		if (currentPosition.y < startPosition.y && currentPosition.x > startPosition.x) {
       
   323 			x = currentPosition.x - width;
       
   324 			y = currentPosition.y; 
       
   325 		}
       
   326 		
       
   327 		/* haut -> gauche */
       
   328 		if (currentPosition.y < startPosition.y && currentPosition.x < startPosition.x) {
       
   329 			x = currentPosition.x;
       
   330 			y = currentPosition.y;
       
   331 		}
       
   332 
       
   333 		/* bas -> gauche */
       
   334 		if (currentPosition.y > startPosition.y && currentPosition.x < startPosition.x) {
       
   335 			x = currentPosition.x
       
   336 			y = currentPosition.y - height;
       
   337 		}
       
   338 		if(!x || !y) { return; }	
       
   339 
       
   340 		rectZone = paper.rect(x, y, width, height);
       
   341 		rectZone.attr({fill: FILL_COLOR, stroke: STROKE_COLOR, opacity: 0.6});
       
   342 	});
       
   343 
       
   344 
       
   345 	paper.mouseup(function () {
       
   346 		if ((drawingMode === FREE_MODE) || pathIsClosed || !rectZone) { return false; }
       
   347 		drawing_path = rectZone;
       
   348 		ShapeResizer.enable_resizer(paper, rectZone, viewPort, currentViewBox);
       
   349 		canDraw = false;
       
   350 		pathIsClosed = true;
       
   351 	});
       
   352 };
       
   353 var attachPointEvents = function (paper) {
       
   354 	if (readOnly) { return; }
       
   355 	paper.click( function(e) {
       
   356 		if (drawingMode === RECT_MODE) {
       
   357 			return true;
       
   358 		}
       
   359 
       
   360 		if (!ENABLE_NEW_NODE) { return true; }
       
   361 		createPoint(paper, e.offsetX, e.offsetY, pointData);
       
   362 	});
       
   363 };
       
   364 
       
   365 
       
   366 var attachZoomEvents = function () {
       
   367 
       
   368 	eventEmitter.on("zoomChanged", function (zoomInfos) {
       
   369 		zoomFactor = zoomInfos.zoomFactor;
       
   370 		currentViewBox = zoomInfos.currentViewBox;
       
   371 		var previousPath = API.getPath();
       
   372 		API.clear();
       
   373 		API.setPath(previousPath);
       
   374 	});
       
   375 
       
   376 }
       
   377 
       
   378 
       
   379 var API = {
       
   380 
       
   381 	getPaper: function () {
       
   382 		return paper;
       
   383 	},
       
   384 
       
   385 	setPath: function (pathString) {
       
   386 		/* redraw the path */
       
   387 		var pathInfos = pathString.split(';');
       
   388 		if ( availableModes.indexOf(pathInfos[1]) === -1) {
       
   389 			/* We assume then it is a free path */
       
   390 			pathInfos[1] = "FREE"; 
       
   391 		}
       
   392 
       
   393 		this.setDrawingMode(pathInfos[1]);
       
   394 		var pathData = pathInfos[0];
       
   395 		
       
   396 		if (!pathData.length) {
       
   397 			return;
       
   398 		}
       
   399 		/* deal with path nomalization x = ImageWith/MaxXBound */
       
   400 		var xRatio = mainImage.attr("width") / viewBoxBounds.X;
       
   401 		var yRatio = mainImage.attr("height") / viewBoxBounds.Y;
       
   402 		
       
   403 		if (isNaN(xRatio) || isNaN(yRatio)) {
       
   404 			new Error('Ratio should be a number.');
       
   405 		}
       
   406 
       
   407 		var transformMatrix = Snap.matrix(xRatio, 0, 0, yRatio, 0, 0);
       
   408 		var path = Snap.path.map(pathData, transformMatrix).toString();
       
   409 		
       
   410 		/* always close path */
       
   411 		if (path.search(/[z|Z]/gi) === -1 ) {
       
   412 			path += "Z";
       
   413 		}
       
   414 		if (pathInfos.length >= 2) {
       
   415 			if (pathInfos[1] === RECT_MODE) {
       
   416 				handleRectPath(path);
       
   417 			}
       
   418 
       
   419 			if (pathInfos[1] === FREE_MODE) {
       
   420 				handleFreePath(path);
       
   421 			}
       
   422 		}
       
   423 	},
       
   424 
       
   425 	setDrawingMode: function (mode) {
       
   426 		
       
   427 		if (availableModes.indexOf(mode) !== -1) {
       
   428 			drawingMode = mode;
       
   429 		}
       
   430 		if (typeof onChangeCallback === "function") {
       
   431 			onChangeCallback(drawingMode);
       
   432 		}
       
   433 
       
   434 		this.clear();
       
   435 	},
       
   436 	
       
   437 	clear: function () {
       
   438 		/* clear previous path, point, handler */
       
   439 		pointData.map(function (point) {
       
   440 			if (point.handler) {
       
   441 				point.handler.remove();
       
   442 			}
       
   443 		});
       
   444 
       
   445 		/*clear path is exists*/
       
   446 		 if (drawing_path) {
       
   447 		 	drawing_path.remove();
       
   448 		 }		
       
   449 		eventEmitter.emit("cutout:clear");
       
   450 		pointData = [];
       
   451 		startPoint = null;
       
   452 		drawing_path = null;
       
   453 		isDragged = false;
       
   454 		enablePoint = true;
       
   455 		pathIsClosed = false;
       
   456 		ENABLE_NEW_NODE = true;
       
   457 	},
       
   458 
       
   459 	getShapeBBox: function () {
       
   460 		var currentPath = this.getPath();
       
   461 		return Snap.path.getBBox(currentPath);
       
   462 	},
       
   463 	
       
   464 	getShape: function () {
       
   465 		return this.getPath();
       
   466 	},
       
   467 
       
   468 	getPath: function () {
       
   469 		/* retourne le chemin */
       
   470 		/* send path and BBox | implement edit and load path */
       
   471 		var path = "";
       
   472 		if (drawing_path) {
       
   473 			if (drawingMode === RECT_MODE) {
       
   474 				var bBox = drawing_path.getBBox();
       
   475 				var transform = drawing_path.transform();
       
   476 
       
   477 				if (!transform.global.length) {
       
   478 					var shapePath = drawing_path.getBBox().path;
       
   479 				}
       
   480 
       
   481 				else {
       
   482 					
       
   483 					var shapeX = drawing_path.node.getAttribute('x');
       
   484 					var shapeY = drawing_path.node.getAttribute('y');
       
   485 					var transformMatrix = transform.totalMatrix;
       
   486 					var fakeShape = paper.rect(transformMatrix.x(shapeX, shapeY),transformMatrix.y(shapeX, shapeY), bBox.width, bBox.height);
       
   487 					shapePath = fakeShape.getBBox().path;
       
   488 					fakeShape.remove();
       
   489 				}
       
   490 
       
   491 				path = Snap.path.toAbsolute(shapePath).toString();
       
   492 
       
   493 			}
       
   494 			else {
       
   495 				path = drawing_path.attr('d');
       
   496 			}
       
   497 		}
       
   498 
       
   499 		var xRatio = viewBoxBounds.X / mainImage.attr("width");
       
   500 		var yRatio = viewBoxBounds.Y / mainImage.attr("height");
       
   501 
       
   502 		if(isNaN(xRatio) || isNaN(yRatio)) {
       
   503 			new Error('ratio should be a number.');
       
   504 		}
       
   505 
       
   506 		if (!path.length) {
       
   507 			path = (drawingMode === RECT_MODE) ? ";RECT" : ";FREE";
       
   508 			return path;
       
   509 		}
       
   510 		var normalizeMatrix = Snap.matrix(xRatio, 0, 0, yRatio, 0, 0);
       
   511 
       
   512 		path = Snap.path.map(path, normalizeMatrix).toString();
       
   513 
       
   514 				/* save the type */
       
   515 		var type = (drawingMode === RECT_MODE) ? ";RECT" : ";FREE";
       
   516 		if (path.search(/[z|Z]/gi) === -1) {
       
   517 			path += " Z";	
       
   518 		}
       
   519 		
       
   520 		path += type;
       
   521 
       
   522 
       
   523 		return path;
       
   524 	}
       
   525 };
       
   526 
       
   527 /* change to a component */
       
   528 export default {
       
   529 
       
   530 	init: function(config) {
       
   531 		mainImage = jQuery(config.wrapperId).find('.main-image').eq(0);
       
   532 		var cutCanvas = jQuery(config.wrapperId).find('.cut-canvas').eq(0);
       
   533 		var path = jQuery(config.wrapperId).find('.image-path').eq(0);
       
   534 
       
   535 		if (typeof config.onDrawingModeChange === 'function') {
       
   536 			onChangeCallback = config.onDrawingModeChange;
       
   537 		}
       
   538 		
       
   539 		if (!mainImage.length) {
       
   540 			throw new Error("The main image Can't be found ...");
       
   541 		}
       
   542 
       
   543 		if (!cutCanvas.length) {
       
   544 			var cutCanvas = jQuery('<svg version="1.1"></svg>').addClass('cut-canvas');
       
   545 			jQuery(config.wrapperId).append(cutCanvas);
       
   546 			cutCanvas.append(mainImage);
       
   547 		}
       
   548 
       
   549 		
       
   550 
       
   551 		cutCanvas.css({
       
   552 			marginLeft: 'auto',
       
   553 			marginRight: 'auto',
       
   554 			width: viewPort.width,
       
   555 			height: viewPort.height,
       
   556 		});
       
   557 		if (typeof config.readOnly === 'boolean' && config.readOnly === true) {
       
   558 			readOnly = true;
       
   559 		}
       
   560 
       
   561 		paper = new Snap(cutCanvas.get(0));
       
   562 
       
   563 		if (path.length) {
       
   564 			jQuery(cutCanvas).append(path);
       
   565 			var pathData = path.attr("d");
       
   566 			API.setPath(pathData);
       
   567 			path.remove();
       
   568 		}
       
   569 		/* enable zoom */
       
   570 		attachZoomEvents();
       
   571 		attachPointEvents(paper);
       
   572 		attachRectEvents(paper);
       
   573 
       
   574 		return API;
       
   575 	}
       
   576 };