--- 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):