|
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 }; |