Fixing drawing component
authorHarris Baptiste <harris.baptiste@iri.centrepompidou.fr>
Fri, 01 Jul 2016 10:40:02 +0200
changeset 43 ccc449ef6f16
parent 42 51257e2701d9
child 44 6730ec4d7e37
Fixing drawing component
.hgignore
src/iconolab/static/iconolab/js/iconolab-bundle/index.html
src/iconolab/static/iconolab/js/iconolab-bundle/package.json
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/Cutout.vue
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/index.js
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/shape-resizer.js
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/svgboard.js
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/taglist/Taglist.vue
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/taglist/template.html
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/Typeahead.vue
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/style.css
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/template.html
src/iconolab/static/iconolab/js/iconolab-bundle/src/main.js
src/iconolab/static/iconolab/js/iconolab-bundle/webpack.config.js
src/iconolab/static/iconolab/js/webpack.config.js
src/iconolab/templates/iconolab/change_annotation.html
src/iconolab/templates/iconolab/detail_annotation.html
src/iconolab/templates/iconolab_base.html
src/iconolab/views.py
--- a/.hgignore	Wed Jun 29 14:56:27 2016 +0200
+++ b/.hgignore	Fri Jul 01 10:40:02 2016 +0200
@@ -6,6 +6,8 @@
 ^src/iconolab/settings/dev\.py$
 ^src/iconolab/static/iconolab/js/node_modules/
 ^src/iconolab/static/iconolab/js/iconolab-bundle/node_modules/
+^src/iconolab/static/iconolab/js/iconolab-bundle/dist/
+
 
 ^web/*
 ^\.pydevproject$
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/index.html	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/index.html	Fri Jul 01 10:40:02 2016 +0200
@@ -5,8 +5,12 @@
     <title>iconolab-bundle</title>
   </head>
   <body>
-    Ajouter un tag: <div id="app" data-tags="radical blaze">
-    </div>
+
+    	<div id="AppContainer">
+    		<cutout></cutout>
+			<typeahead></typeahead>    		
+    	</div>
+
     <script src="dist/build.js"></script>
   </body>
 </html>
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/package.json	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/package.json	Fri Jul 01 10:40:02 2016 +0200
@@ -5,10 +5,15 @@
   "private": true,
   "scripts": {
     "dev": "webpack-dev-server --inline --hot",
+    "start": "webpack --progress --colors --watch",
     "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
   },
   "dependencies": {
-    "vue": "^2.0.0-alpha.2",
+    "import": "0.0.6",
+    "loader": "^2.1.1",
+    "snapsvg": "^0.4.0",
+    "vue": "^2.0.0-alpha.8",
+    "vue-loader": "^9.1.1",
     "vue-resource": "^0.9.1",
     "vue-typeahead": "^2.1.0"
   },
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/Cutout.vue	Fri Jul 01 10:40:02 2016 +0200
@@ -0,0 +1,99 @@
+<script>
+
+	import svgboard from './svgboard'
+	import Typeahead from '../typeahead/Typeahead.vue'
+	
+	export default {
+
+		el: '#drawing-zone',
+		MODE_RECT: 'RECT',
+		MODE_FREE: 'FREE', 
+
+		components: {typeahead: Typeahead},
+		data: { 
+			mode:"",
+			isRect: true,
+			normalizePath: "",
+			readOnly: false,
+			formView: true,
+			useClipPath: false,
+			transformMatrix: "",
+			fragmentPath: "",
+			displayMask: false
+		},
+
+		mounted () {
+			var self = this;
+			this.initialDrawingMode = null;
+			this.drawingComponent = svgboard.init({ 
+				wrapperId: '#iconolab-image-wrapper',
+					actionWrapper: '#action-wrapper',
+					readOnly: false,
+					onDrawingModeChange: function (mode) {
+						self.setDrawingMode(mode, false);
+					}
+			});
+
+			this.showForm();	
+		},
+
+		methods: {
+
+			
+			setDrawingMode: function (mode, updateComponent) {
+				if (!this.initialDrawingMode) {
+					this.initialDrawingMode = mode;//useful for cancel
+				}
+				var updateComponent = (typeof updateComponent === "boolean") ? updateComponent: true;
+				this.mode = this.$options['MODE_' + mode];
+				this.isRect = (this.mode === this.$options.MODE_RECT) ? true: false;
+				if (updateComponent) {
+					this.drawingComponent.setDrawingMode(this.mode);
+				}
+			},
+			
+			cancel: function () {
+				this.formView = true;
+				var currentPath = this.$refs.currentPath.getAttribute("d");
+				if (!currentPath.length || !this.initialDrawingMode) { return; } {
+					currentPath += ";" + this.initialDrawingMode; 
+					this.drawingComponent.setPath(currentPath);
+				}
+			},
+
+			showEditor: function () {
+				this.formView = false;
+			},
+
+			highLightZone: function () {
+				if (!this.displayMask) {
+					this.displayMask = true;
+				}
+				else {
+					this.displayMask = false;
+				}
+			},
+
+			displayEditedPath: function () {
+				var normalizePath = this.drawingComponent.getPath();
+			},
+
+			showForm: function () {
+				this.normalizePath = this.drawingComponent.getPath();
+				var smallImage = this.$refs.smallImage;
+				console.log(this.$refs);
+				this.formView = true;
+				var xRatio = smallImage.width / 100;
+				var yRatio = smallImage.height / 100;
+				var transformMatrix = [xRatio, 0, 0, yRatio, 0, 0].join(',');
+				this.transformMatrix ="matrix(" + transformMatrix + ")";
+				this.fragmentPath = this.normalizePath.split(';')[0];
+			},
+
+			clear: function () {
+				this.drawingComponent.clear();
+			}
+		}
+	}
+</script>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/index.js	Fri Jul 01 10:40:02 2016 +0200
@@ -0,0 +1,9 @@
+
+import CutoutVue from './Cutout.vue'
+
+export default {
+
+		init: function () {
+			new Vue(CutoutVue);
+		}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/shape-resizer.js	Fri Jul 01 10:40:02 2016 +0200
@@ -0,0 +1,129 @@
+/* Enabling us to resize a shape width a handler 
+	#http://stackoverflow.com/questions/32390028/how-to-drag-and-resize-svg-rectangle-using-cursor-types
+*/
+import EventEmitter from "event-emitter"
+
+var eventEmitter = EventEmitter({});
+var ShapeResizer = function (paper, shape) {
+	this.paper = paper;
+	this.shape = shape;
+	this.handlers = [];
+	this.isResizing = false;
+	this.currentPosition = {};
+	this.HANDLER_SIZE = 8;
+	this.SHAPE_MIN_SIZE = 20;
+	this.states = {};
+	this.noop = function (){}
+	this.init();
+}
+
+var api = ShapeResizer.prototype = {
+
+	init: function () {
+		this.showHandlers();
+	},
+
+	showHandlers: function () {
+		/* show handler here */
+		var bbox = this.shape.getBBox();
+		var handleX = bbox.x - this.HANDLER_SIZE/2;
+		var handleY = bbox.y - this.HANDLER_SIZE/2;
+		var handler = this.paper.rect(handleX, handleY, this.HANDLER_SIZE, this.HANDLER_SIZE).attr({fill: 'red'});
+		var handlerInfos = {position: "t_r", handler: handler};
+		this.handlers.push(handlerInfos);
+		this.shapesGroup = this.paper.g(this.shape, handler);
+		this.attachEvents();
+	},
+
+	/*one handlers */
+	updateShapePositions: function (handlerData, dx, dy) {
+		//start
+		var handlerBBox = handlerData.handler.getBBox();
+		var shapeBBox = this.shape.data("origBbox");
+		var newX = handlerBBox.x + this.HANDLER_SIZE / 2;
+		var newY = handlerBBox.y + this.HANDLER_SIZE / 2;
+
+		/*to the right => reduce the size */
+		var newWidth = (dx > 0) ? shapeBBox.width - dx : shapeBBox.width + Math.abs(dx); 
+		var newHeight = (dy > 0) ? shapeBBox.height - dy : shapeBBox.height + Math.abs(dy);
+		var transformValue = this.shape.data('origTransform') + (this.shape.data('origTransform') ? "T" : "t") + [dx, dy];
+		this.shape.attr({'transform': transformValue, width: newWidth, height: newHeight});
+	},
+
+	dragEvents: {
+		onStart: function (handlerData, dx, dy, e) {
+			this.startPosition = {x: e.clientX, y: e.clientY};
+			this.isResizing = true;
+			this.currentPosition = {};
+			handlerData.handler.data("origTransform", handlerData.handler.transform().local);
+			this.shape.data("origBbox", this.shape.getBBox());
+			this.shape.data("origTransform", this.shape.transform().local);
+		},
+
+		onMove: function (handlerData, dx, dy, x, y, e) {
+			var transformValue = handlerData.handler.data('origTransform') + (handlerData.handler.data('origTransform') ? "T" : "t") + [dx, dy];
+			this.currentPosition.x = e.clientX;
+			this.currentPosition.y = e.clientY;
+			/* deal with boundaries */
+			if (! this.checkBondaries(dx, dy)) { return; }
+			handlerData.handler.attr({ transform: transformValue});
+			this.updateShapePositions(handlerData, dx, dy);
+		},
+
+		onStop: function () {
+			this.isResizing = false;
+			this.startPosition = {};
+			this.currentPosition = {};
+		}
+	},
+	
+	checkBondaries: function (dx, dy) {
+		var result = true;
+
+		if (this.shape.data("origBbox").width - dx <=  this.SHAPE_MIN_SIZE) {
+			result = false;
+		}
+
+		if (this.shape.data("origBbox").height - dy <= this.SHAPE_MIN_SIZE) {
+			result = false;
+		}
+
+		return result;
+	},
+
+	destroy: function () {
+		this.handlers.map(function (handlerData) {
+			handlerData.handler.remove();
+		});
+		delete this;
+	},
+
+	attachEvents: function () {
+		var self = this;
+		this.handlers.map(function (handlerData) {
+			handlerData.handler.drag(
+				self.dragEvents.onMove.bind(self, handlerData),
+			 	self.dragEvents.onStart.bind(self, handlerData),
+				self.dragEvents.onStop.bind(self, handlerData));
+		});
+
+		eventEmitter.on("cutout:clear", function () {
+			self.destroy();
+		});
+
+		this.shapesGroup.drag(function (dx, dy) {
+			if (self.isResizing) { return; }
+			var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy];
+			this.transform(transformValue);
+		}, function () {
+			this.data('origTransform', this.transform().local);
+		}, this.noop);
+	},
+}
+
+export default {
+
+	apply_resize : function (paper, rect) {
+		new ShapeResizer(paper, rect);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/svgboard.js	Fri Jul 01 10:40:02 2016 +0200
@@ -0,0 +1,525 @@
+
+
+var Snap = require('snapsvg');
+//import Snap from 'snapsvg'
+import ShapeResizer from './shape-resizer'
+import EventEmitter from 'event-emitter'
+var eventEmitter = EventEmitter({});
+
+/* custom plugin */
+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 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 = 6;
+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.apply_resize(paper, drawing_path);
+};
+
+
+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,
+		"stroke-width": 3,
+		fill: "white",
+		opacity: 0.1
+	});
+	
+	/* bring all handler to front */
+	pointData.map(function (point) {
+		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 onMoveHandler = function (dx, dy, posX, posY, e) {
+	isDragged = true;
+	/* update point then update the view */
+	var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy];
+	this.attr({ transform: transformValue});
+	var boxSize = this.getBBox();
+
+	var wasUpdated = updatePointPosition(this.data('point'), boxSize.x + (HANDLE_SIZE / 2) , boxSize.y + (HANDLE_SIZE / 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 handleX = point.x - HANDLE_SIZE/2;
+	var handleY = point.y - HANDLE_SIZE/2;
+
+	handler = p.rect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE);
+
+	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.apply_resize(paper, rectZone);
+		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 API = {
+	
+	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.width() / viewBoxBounds.X;
+		var yRatio = mainImage.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;
+	},
+
+	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.globalMatrix;
+					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.width();
+		var yRatio = viewBoxBounds.Y / mainImage.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('img').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 (!cutCanvas.length) {
+			var cutCanvas = jQuery('<svg version="1.1"></svg>').addClass('cut-canvas');
+			jQuery(config.wrapperId).append(cutCanvas);
+		}
+
+		if (!mainImage) {
+			new Error(config.wrapperId + "Can't be found ...");
+		}
+
+		cutCanvas.css({
+			position: 'absolute', 
+			top: '0px', 
+			left: '15px',
+			marginLeft: 'auto',
+			marginRight: 'auto',
+			width: mainImage.width(),
+			height: mainImage.height(),
+			viewBox: '0 0 100 100'
+		});
+
+		if (typeof config.readOnly === 'boolean' && config.readOnly === true) {
+			readOnly = true;
+		}
+
+		paper = new Snap(cutCanvas.get(0));
+
+		if (path.length) {
+			jQuery(cutCanvas).append(path);
+			API.setPath(path.attr("d"));
+		}
+		
+		attachPointEvents(paper);
+		attachRectEvents(paper);
+
+		return API;
+	}
+};
\ No newline at end of file
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/taglist/Taglist.vue	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/taglist/Taglist.vue	Fri Jul 01 10:40:02 2016 +0200
@@ -1,5 +1,4 @@
 <template src='./template.html'></template>
-
 <script>
 
 import { generateId } from '../utils'
@@ -7,10 +6,11 @@
 export default {
 	data () {
 		return {
-			tags: []
+			tags: [],
+			readOnly: false
 		}
 	},
-
+	
 	methods: {
 		removeTag: function (index) {
 			this.tags.$remove(index);
@@ -21,11 +21,19 @@
 		tagAlreadyExists (tag) {
 			var result = false;
 			var found = this.tags.find(function (userTag) {
-				if (userTag.tag_url === tag.tag_url) {
-					return true;
-				}
+
+				if (!userTag.tag_link) {
+
+					if (userTag.tag_label === tag.tag_label) {
+						return true;
+					}
+
+				} else {
+					if (userTag.tag_link === tag.tag_link) {
+						return true;
+					}
+				}				
 			});	
-
 			if (found) {
 				var tagNode = this.$refs[found.id][0];
 				tagNode.style.border = "1px solid red";
@@ -37,11 +45,8 @@
 			return result;
 		},
 
-		highlight () {
-
-		},
-		
 		setTags (tagArrays){
+			
 			if (!Array.isArray(tagArrays)) { new Error('setTags expects an array!'); }
 			var self = this;
 			tagArrays.map(function (tag) {
@@ -50,6 +55,7 @@
 		},
 
 		addTag (tag) {
+			
 			if (this.tagAlreadyExists(tag)) { return false; }
 
 			if (!tag || !tag.hasOwnProperty('tag_label')) { return; }
@@ -67,7 +73,7 @@
 			
 			this.tags.map(function (tag) {
 				var tagItem = {};
-				tagItem.tag_input = (typeof tag.tag_url === "string") ? tag.tag_url: tag.tag_label;
+				tagItem.tag_input = (typeof tag.tag_link === "string" && tag.tag_link.length) ? tag.tag_link: tag.tag_label;
 				tagItem.accuracy = tag.accuracy;
 				tagItem.relevancy = tag.relevancy;
 				result.push(tagItem);
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/taglist/template.html	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/taglist/template.html	Fri Jul 01 10:40:02 2016 +0200
@@ -1,14 +1,25 @@
-<ul class="tags-wrapper">
-	<li v-for="(tag, index) in tags" :ref="tag.id">
-		<span v-text="tag.tag_label"></span>{{ tag.id }}
+<div>
+
+	<ul v-show="!readOnly" class="tags-wrapper list-unstyled">
+		<li class="tag-item" v-for="(tag, index) in tags" :ref="tag.id">
+			<i class="fa fa-tag"></i> <strong><span v-text="tag.tag_label"></span></strong>
+
+			Précision: 	<select @change="updateAccuracy($event, tag)">
+							<option v-for="(no, index) in [1,2,3,4,5]" v-bind:selected="tag.accuracy == no" v-bind:value="no">{{ no }}</option>
+						</select>
 
-		Précision: 	<select @change="updateAccuracy($event, tag)">
-						<option v-for="(no, index) in [1,2,3,4,5]" v-bind:selected="tag.accuracy == no" v-bind:value="no">{{ no }}</option>
-					</select>
+			Pertinence: <select @change="updatePertinence($event, tag)">
+							<option v-for="(no, index) in [1,2,3,4,5]" v-bind:selected="tag.relevancy == no" v-bind:value="no">{{ no }}</option>
+						</select>
+						<a @click="removeTag(tag)"><i class="fa fa-trash"></i> </a>
+		</li>
+	</ul>
 
-		Pertinence: <select @change="updatePertinence($event, tag)">
-						<option v-for="(no, index) in [1,2,3,4,5]" v-bind:selected="tag.relevancy == no" v-bind:value="no">{{ no }}</option>
-					</select>
-					<a @click="removeTag(tag)"> [Effacer] </a>
-	</li>
-</ul>
\ No newline at end of file
+	<ul class="list-inline" v-show="readOnly">
+		<li v-for="(tag, index) in tags"  href="#">
+			<i class="fa fa-tag"></i> <span class="label label-info">{{ tag.tag_label }}</span>
+			 | précision <span class="badge">{{ tag.accuracy }}</span>
+			 | pertinence <span class="badge">{{ tag.relevancy }}</span>
+		</li>
+	</ul>
+</div>
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/Typeahead.vue	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/Typeahead.vue	Fri Jul 01 10:40:02 2016 +0200
@@ -1,34 +1,41 @@
 <template src='./template.html'></template>
-<style src='./style.css'></style>
+<style scoped src='./style.css'></style>
 
 <script>
 	import typeahead from 'vue-typeahead'
-	import data from './data'
 	import Taglist from '../taglist/Taglist.vue'
 
 	var autoCompletePath = "http://lookup.dbpedia.org/api/search/PrefixSearch?MaxHits=5";
 
-	/* get data from parents */
-	var defaultTags = [
-		{tag_label: 'Peinture', tag_url:'http//iconolab/tags/peinture', accuracy:1, relevancy: 3},
-		{tag_label: 'Atelier', tag_url:'http://iconolab/tags/atelier', accuracy:3, relevancy: 5},
-		{tag_label: 'détail', tag_url:'http://iconolab/tags/detail', accuracy:2, relevancy: 2}
-	];
-
 	var parentsMethods = {
 		reset: typeahead.methods.reset
 	};
 
+	var get = function (url, data) {
+		var dfd = jQuery.Deferred();
+		var promise = jQuery.getJSON(url, data).done( function (response) {
+			var envelope = {};
+			envelope.data = response;
+			dfd.resolve(envelope);
+		}).fail(dfd.fail);
+		return dfd.promise();
+	}
+
 	export default {
-		extends: typeahead,
+		mixins: [typeahead],
 		components: { 'taglist' : Taglist },
+		
+		props: ['tags', 'read-only'],
 
 		mounted() {
 			this.taglist = this.$refs.taglist;
-			this.taglist.setTags(defaultTags);
+			this.taglist.readOnly = this.readOnly;
+			if (Array.isArray(this.tags) && this.tags.length) {
+				this.taglist.setTags(this.tags);
+			}
+
 		},
 
-
 		data() {
 			return {
 				src: autoCompletePath,
@@ -50,6 +57,12 @@
       			}
       		},
       		
+      		fetch() {
+				var query = {};
+				query[this.queryParamName] = this.query;
+				return get(this.src, query);
+			},
+
       		reset () {
       			this.showAddButton = false;
       			parentsMethods.reset.call(this);
@@ -64,7 +77,7 @@
       				rawResults.map(function (item) {
       					var tagItem = {};
 						tagItem.tag_label = item.label;
-						tagItem.tag_url = item.uri; 
+						tagItem.tag_link = item.uri; 
 						tagItem.accuracy = 1;
 						tagItem.relevancy = 1; 
 						results.push(tagItem);
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/style.css	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/style.css	Fri Jul 01 10:40:02 2016 +0200
@@ -24,16 +24,11 @@
   outline: 0;
   box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px #4fc08d;
 }
+.tag-item {border: 1px solid red;}
 .fa-times {
   cursor: pointer;
 }
-i {
-  float: right;
-  position: relative;
-  top: 30px;
-  right: 29px;
-  opacity: 0.4;
-}
+
 ul {
   /*position: absolute;*/
   padding: 0;
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/template.html	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/template.html	Fri Jul 01 10:40:02 2016 +0200
@@ -1,15 +1,7 @@
 <div>
-  <!-- optional indicators -->
-  <i class="fa fa-spinner fa-spin" v-if="loading"></i>
-  <template v-else>
-    <i class="fa fa-search" v-show="isEmpty"></i>
-    <i class="fa fa-times" v-show="isDirty" @click="reset"></i>
-  </template>
-
   <taglist ref="taglist"></taglist>
-
   <!-- the input field -->
-  <input type="text"
+  <input v-show="!readOnly" type="text"
          placeholder="..."
          autocomplete="off"
          v-model="query"
@@ -20,10 +12,10 @@
          @keydown.esc="reset"
          @input="update"/>
 
-  <button @click="addTag" v-show="showAddButton">Créer ce tag</button>
+  <a style="border: 1px solid red" @click="addTag" v-show="showAddButton"><i class="fa fa-plus"></i> Créer ce tag</a>
 
   <!-- the list -->
-  <ul v-show="hasItems">
+  <ul v-show="hasItems || !readOnly">
     <li v-for="(item, index) in items" :class="activeClass(index)" @mousedown="hit" @mousemove="setActive(index)">
       <span v-text="item.tag_label"></span>
     </li>
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/main.js	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/main.js	Fri Jul 01 10:40:02 2016 +0200
@@ -1,12 +1,17 @@
-import Vue from 'vue'
-import resource from 'vue-resource'
-import Typeahead from './components/typeahead/Typeahead.vue'
+import 'expose?Vue!vue/dist/vue'
 import "expose?jQuery!jquery"
 
-
-Vue.use(resource)
+import VueResource from 'vue-resource'
+import Typeahead from './components/typeahead/Typeahead.vue'
+import Cutout from './components/cutout'
 
-new Vue({
-  el: '#app',
-  render: h => h(Typeahead)
-})
+var iconolab = {
+	Cutout : Cutout,
+	VueComponents : {
+		Typeahead: Typeahead,
+	}
+};
+
+if (!window.iconolab) {
+	window.iconolab = iconolab;
+}
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/webpack.config.js	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/webpack.config.js	Fri Jul 01 10:40:02 2016 +0200
@@ -1,6 +1,5 @@
 var path = require('path')
 var webpack = require('webpack')
-
 module.exports = {
   entry: './src/main.js',
   output: {
@@ -28,7 +27,11 @@
         query: {
           name: '[name].[ext]?[hash]'
         }
-      }
+      },
+      {
+        test: require.resolve('snapsvg'),
+        loader: 'imports-loader?this=>window,fix=>module.exports=0'
+      },
     ]
   },
   devServer: {
--- a/src/iconolab/static/iconolab/js/webpack.config.js	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/static/iconolab/js/webpack.config.js	Fri Jul 01 10:40:02 2016 +0200
@@ -1,5 +1,5 @@
 var path = require('path');
-var webpack=  require('webpack');
+var webpack = require('webpack');
 
 module.exports = {
 	entry: './main.js',
--- a/src/iconolab/templates/iconolab/change_annotation.html	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/templates/iconolab/change_annotation.html	Fri Jul 01 10:40:02 2016 +0200
@@ -7,7 +7,6 @@
 {% block content %}
 
 	<div id="drawing-zone" class="row" style="padding-top: 10px; border:1px solid orange">
-		
 		<div v-show='!formView' style="display:none" class="editor-wrapper col-md-12">
 			<div class='col-md-2'>
 				<ul class="form-drawing-wrapper list-inline">
@@ -26,7 +25,7 @@
 			</div>
 			
 			<div class="col-md-8">
-				<div v-el:image id="iconolab-image-wrapper">
+				<div ref="image" id="iconolab-image-wrapper">
 					{% thumbnail image.media "x800" crop="center" as im %}
 	    				<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
 	    				<path class="image-path" d="{% if annotation %}{{ annotation.current_revision.fragment }}{% endif %}"></path>
@@ -45,15 +44,15 @@
 			<div class="col-xs-6">
 				<div class="small-image-wrapper" style="position: relative">
 					{% thumbnail image.media "x300" crop="center" as im %}
-						<img v-el:small-image @click="showEditor" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+						<img ref="smallImage" @click="showEditor" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
 
 						<svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
 							
 							<defs>
-								<mask xmlns="http://www.w3.org/2000/svg" id="smallImage">
-									<rect x="0" y="0" width="{{ im.width }}", height="{{ im.height }}" fill="white"/>
+								<mask id="smallImage">
+									<rect x="0" y="0" width="{{ im.width }}" height="{{ im.height }}" fill="white"/>
 									<g v-bind:transform="transformMatrix">
-										<path v-el:current-path v-bind:d="fragmentPath"></path>
+										<path ref="currentPath" v-bind:d="fragmentPath"></path>
 									</g>
 								</mask>	
 							</defs>
@@ -62,9 +61,9 @@
 								<path v-bind:d="fragmentPath" opacity="0.7" fill="orange"></path>
 							</g>
 
-							<rect v-show="displayMask" v-el:small-mask x="0" y="0" mask="url(#smallImage)" opacity="0.7" fill="white" width="{{ im.width }}" height="{{ im.height }}"></rect>
+							<rect v-show="displayMask" ref="smallMask" x="0" y="0" mask="url(#smallImage)" opacity="0.7" fill="white" width="{{ im.width }}" height="{{ im.height }}"></rect>
 						</svg>
-						
+					
 					{% endthumbnail %}
 				</div>
 				<ul class="inline">
@@ -89,9 +88,11 @@
                         name="{{ form.description.name }}"
                         id="id_{{ form.description.name }}" >{% if form.description.value %}{{ form.description.value}}{% endif %}</textarea>
                     </fieldset>
-                    <fieldset class="form-group">
+                    <fieldset id="test" class="form-group">
                       <label class="control-label" for="id_{{ form.tags.name }}">{{ form.tags.label }}</label>
+                      <typeahead :tags="{{ tags_data  }}"></typeahead>
                     </fieldset>
+
                     <fieldset class="form-group">
                       <label class="control-label" for="id_{{ form.comment.name }}">{{ form.comment.label }}</label>
                       <textarea class="form-control"
@@ -99,115 +100,18 @@
                         id="id_{{ form.comment.name }}" ></textarea>
                     </fieldset>
         			<input v-model="normalizePath" type="hidden" name="fragment"></input>
-                    <input id="tags_input" type="hidden" name="tags" value="{{ form.tags_json }}"></input>
         			<button type="submit" class="save btn btn-default">Enregister</button>
                     <a class="btn btn-default" href="{% if annotation %}{% url 'annotation_detail' collection_name image_guid annotation_guid %}{% else %}{% url 'image_detail' collection_name image_guid %}{% endif %}" role="button">Retour</a>
     			</form>
 			</div>
 			
 		</div>
-
 	</div>
 
 {% endblock %}
 
 {% block footer_js %}
 	<script>
-		var drawingVue = new Vue({
-			el: '#drawing-zone',
-			MODE_RECT: 'RECT',
-			MODE_FREE: 'FREE', 
-
-			data: {
-				mode:"",
-				isRect: true,
-				normalizePath: "",
-				readOnly: false,
-				formView: true,
-				useClipPath: false,
-				transformMatrix: "",
-				fragmentPath: "",
-				displayMask: false
-
-			},
-
-			init: function () {
-				var self = this;
-				this.initialDrawingMode = null;
-				this.drawingComponent = iconolab.initCutoutComponent({ 
-					wrapperId: '#iconolab-image-wrapper',
-						actionWrapper: '#action-wrapper',
-						readOnly: false,
-						onDrawingModeChange: function (mode) {
-							self.$nextTick(function () {
-								self.setDrawingMode(mode, false);
-							});
-						}
-				});
-
-				this.$nextTick(function () {
-					this.showForm();	
-				});
-			},
-
-			methods: {
-
-				setDrawingMode: function (mode, updateComponent) {
-					if (!this.initialDrawingMode) {
-						this.initialDrawingMode = mode;//useful for cancel
-					}
-					var updateComponent = (typeof updateComponent === "boolean") ? updateComponent: true;
-					this.mode = this.$options['MODE_' + mode];
-					this.isRect = (this.mode === this.$options.MODE_RECT) ? true: false;
-					if (updateComponent) {
-						this.drawingComponent.setDrawingMode(this.mode);
-					}
-				},
-				
-				cancel: function () {
-					this.formView = true;
-					var currentPath = this.$els.currentPath.getAttribute("d");
-					if (!currentPath.length || !this.initialDrawingMode) { return; } {
-						currentPath += ";" + this.initialDrawingMode; 
-						this.drawingComponent.setPath(currentPath);
-					}
-				},
-
-				showEditor: function () {
-					this.formView = false;
-				},
-
-				highLightZone: function () {
-					if (!this.displayMask) {
-						this.displayMask = true;
-					}
-					else {
-						this.displayMask = false;
-					}
-					//this.maskFill = "orange";
-					//this.fragmentFill = "none";
-				},
-
-				displayEditedPath: function () {
-					/* path to save */
-					var normalizePath = this.drawingComponent.getPath();
-				},
-
-				showForm: function () {
-					this.normalizePath = this.drawingComponent.getPath();
-					var smallImage = this.$els.smallImage;
-					this.formView = true;
-					var xRatio = smallImage.width / 100;
-					var yRatio = smallImage.height / 100;
-					var transformMatrix = [xRatio, 0, 0, yRatio, 0, 0].join(',');
-					this.transformMatrix ="matrix(" + transformMatrix + ")";
-					this.fragmentPath = this.normalizePath.split(';')[0];
-				},
-
-				clear: function () {
-					this.drawingComponent.clear();
-				}
-			}
-		}); 
+		iconolab.Cutout.init();
 	</script>
 {% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/detail_annotation.html	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_annotation.html	Fri Jul 01 10:40:02 2016 +0200
@@ -29,11 +29,12 @@
 				</div>
 			</div>
 
-			<div class='col-xs-6' style="">
-                <h4>Annotation</h4>
+			<div id="detail-annotation" class='col-xs-6' style="">
+        <h4>Annotation</h4>
 				<p> <strong>Title:</strong> {{ annotation.current_revision.title }}</p>
 				<p> <strong>Description:</strong> {{ annotation.current_revision.description }}</p>
-				<p> <strong>Tags:</strong> {{ tags_data }}</p>
+        <p><strong>Tags:</strong></p>
+        <typeahead :read-only="1" :tags="{{ tags_data }}"></typeahead>
 
 				<a href="{% url 'annotation_edit' collection_name image_guid annotation_guid  %}">Editer</a> | 
 				<a href="{% url 'annotation_edit' collection_name image_guid annotation_guid %}">Proposer une révision</a>
@@ -89,3 +90,13 @@
         <div></div>
 	</div>
 {% endblock %}
+
+{% block footer_js %}
+  <script>
+    new Vue({
+      el: "#detail-annotation",
+      components: {'Typeahead': iconolab.VueComponents.Typeahead }, 
+      methods: { yo: function(){alert(1);} }
+    });
+  </script>
+{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab_base.html	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/templates/iconolab_base.html	Fri Jul 01 10:40:02 2016 +0200
@@ -7,7 +7,8 @@
 		<title>{% block title %} {% endblock %}</title>
 		
 		{% block main_js %}
-			<script src="{% static 'iconolab/js/dist/bundle.js' %}" type="text/javascript"></script>
+			<!--<script src="{% static 'iconolab/js/dist/bundle.js' %}" type="text/javascript"></script>-->
+			<script src="{% static 'iconolab/js/iconolab-bundle/dist/build.js' %}" type="text/javascript"></script>
 		{% endblock %}
 		{% block page_js %} {% endblock %}
 
@@ -28,6 +29,8 @@
 	    	{% include "partials/header.html"%}
 			{% block content %} {% endblock %}
 	    </div>
-	    {% block footer_js %} {% endblock %}
+	    {% block footer_js %}
+
+	    {% endblock %}
 	</body>
 </html>
\ No newline at end of file
--- a/src/iconolab/views.py	Wed Jun 29 14:56:27 2016 +0200
+++ b/src/iconolab/views.py	Fri Jul 01 10:40:02 2016 +0200
@@ -79,6 +79,7 @@
 		context = self.get_context_data(**kwargs)
 		context["image"] = image
 		context["form"] = annotation_form
+		context["tags_data"] = "[]"
 		return render(request, 'iconolab/change_annotation.html', context) 
 	
 	def post(self, request, *args, **kwargs):
@@ -105,6 +106,8 @@
 				user_name = request.user.username
 			)
 			return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': new_annotation.annotation_guid}))(request)
+		print(annotation_form.errors)
+		return HttpResponse("error")
 
 
 class ShowAnnotationView(View, ContextMixin):