src_js/iconolab-bundle/src/components/editor/ShapeFree.vue
author Alexandre Segura <mex.zktk@gmail.com>
Fri, 17 Mar 2017 18:05:37 +0100
changeset 448 1c07ea9abcd5
parent 447 3ad8513b751a
child 509 efa81cbd61e9
permissions -rw-r--r--
Display help text in SVG path.

<template>
    <svg>
        <defs>
            <path v-bind:id="annotationHref" ref="path" v-bind:d="path" class="path"></path>
        </defs>
        <g ref="g">
            <use v-bind:xlink:href="annotationSelector" v-bind:stroke-width="strokeWidth * 2" class="stroke-bg" filter="url(#shadow)" />
            <use v-bind:xlink:href="annotationSelector" v-bind:stroke-width="strokeWidth" class="stroke-fg" />
            <use v-bind:xlink:href="annotationSelector" class="overlay" v-bind:class="{ active: !originalAnnotation }" />
            <circle ref="handlers"
                v-for="(point, key) in points"
                :key="key"
                v-show="!readonly"
                v-bind:data-key="key"
                v-bind:cx="point.x"
                v-bind:cy="point.y"
                v-bind:r="strokeWidth * 2"
                v-bind:stroke-width="strokeWidth"
                v-bind:class="{ handler: true, 'handler--first': key === 0 &amp;&amp; !closed }"></circle>
            <text ref="text"
                v-if="!originalAnnotation && !closed && points.length > 3"
                v-bind:x="text.x" v-bind:y="text.y"
                font-family="Verdana"
                v-bind:font-size="strokeWidth * 4"
                class="text">
                Double-cliquez pour fermer la zone
            </text>
        <g>
    </svg>
</template>

<script>

    import Snap from 'snapsvg'
    import tooltip from './mixins/tooltip'
    import save from './mixins/save'

    export default {
        mixins: [ tooltip, save ],
        props: [
            'paper',
            'original-annotation',
            'original-path',
            'readonly',
            'stroke-width',
        ],
        data() {
            return {
                path: '',
                closed: false,
                points: [],
                text: { x: 0, y: 0 }
            }
        },
        computed: {
            annotationHref: function() {
                if (this.originalAnnotation) {
                    return 'annotation-path-' + this.originalAnnotation.annotation_guid;
                } else {
                    return 'annotation-path-new';
                }
            },
            annotationSelector: function() {
                return '#' + this.annotationHref;
            },
        },
        mounted() {
            var g = new Snap(this.$refs.g);
            if (this.originalPath) {
                this.fromSVGPath(this.originalPath, false);
                g.click(() => this.$emit('click'));
            } else {
                g.dblclick(() => this.closePath());
            }
        },
        watch: {
            closed: function(closed) {
                if (closed) {
                    this.path += ' Z';
                }
            },
            // Redraw the path when the points have changed
            points: function(points) {

                var path = "M";

                if (points.length <= 1) {
                    return;
                }

                path += points[0].x + ',' + points[0].y;

                for (var i = 0; i < points.length; i++) {
                    if (i == 0) continue;

                    var pointInfos = points[i];
                    var lPath = "L" + pointInfos.x + "," + pointInfos.y;
                    path += " " + lPath;
                }

                if (this.closed) { path += ' Z'; }

                this.path = path;

                this.$nextTick(() => {
                    var group = new Snap(this.$refs.g);
                    var text = new Snap(this.$refs.text);
                    this.text = {
                        x: group.getBBox().x + (group.getBBox().width / 2) - (text.getBBox().width / 2),
                        y: group.getBBox().y + (group.getBBox().height / 2) - (text.getBBox().height / 2)
                    }
                });
            }
        },
        methods: {

            addPoint: function(x, y) {

                this.points.push({ x: x, y: y });

                // Attach events to last point once DOM has been refreshed
                // @link https://vuejs.org/v2/guide/reactivity.html
                this.$nextTick(() => {
                    var handler = this.$refs.handlers[this.$refs.handlers.length - 1];
                    this.addResizeHandler(handler);
                });
            },

            clear: function() {
                this.destroyTooltip();
                Object.assign(this, {
                    points: [],
                    closed: false,
                    path: ''
                });
            },

            getTooltipTarget: function() {
                return this.$refs.path;
            },

            fromSVGPath: function(pathString, tooltip) {

                var segments = Snap.parsePathString(pathString);
                var points = [];

                // Don't use this.addPoint to avoid
                // race condition when registering events
                segments.map((segment) => {
                    if (segment[0] !== 'Z') {
                        points.push({ x: segment[1], y: segment[2] });
                    }
                });

                this.points = points;
                this.closed = true;

                this.$nextTick(() => {
                    this.addResizeHandlers();
                    if (tooltip) {
                        // FIXME Race condition with destroy
                        setTimeout(() => this.addTooltip(), 250);
                    }
                });
            },

            toSVGPath: function() {
                return this.$parent.normalizePath(this.path) + ';FREE'
            },

            closePath: function() {
                this.closed = true;
                this.$nextTick(() => {
                    // FIXME Race condition with destroy
                    setTimeout(() => this.addTooltip(), 250);
                });
            },

            addResizeHandler: function(handler) {

                var self = this;

                var circle = new Snap(handler);

                var isDragged = false;

                circle.click((e) => {
                    var key = parseInt(circle.attr('data-key'), 10);
                    if (key === 0 && this.points.length > 2) {
                        this.closePath();
                    }
                });

                // Remove point on double click
                circle.dblclick((e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    var circle = new Snap(e.target);
                    var key = parseInt(circle.attr('data-key'), 10);
                    this.points.splice(key, 1);
                });

                var dragEvents = {
                    onMove: function(dx, dy, x, y, e) {

                        isDragged = true;

                        var offset = self.$parent.computeOffset(e);
                        this.attr({ cx: offset.x, cy: offset.y });

                        var key = parseInt(this.attr('data-key'), 10);

                        // Must use splice for reactivity to work
                        // @see https://vuejs.org/v2/guide/list.html#Mutation-Methods
                        self.points.splice(key, 1, { x: offset.x, y: offset.y });
                    },
                    onStart: () => this.hideTooltip(),
                    onEnd: function(e) {
                        if (!isDragged) { return; }

                        isDragged = false;
                        self.showTooltip();
                    }
                }

                circle.drag(dragEvents.onMove, dragEvents.onStart, dragEvents.onEnd);
            },

            addResizeHandlers: function() {
                this.$refs.handlers.forEach((handler) => this.addResizeHandler(handler));
            }
        }
    }

</script>

<style scoped>
.handler {
    fill: #fff;
    stroke: #000;
}
.path {
    stroke: inherit;
    stroke-width: inherit;
    fill: inherit;
    transform: inherit;
}
.text {

}
.stroke-fg {
    stroke: #000;
    fill: transparent;
}
.overlay {
    fill: transparent;
}
.overlay.active,
.overlay:hover {
    cursor: pointer;
    fill: #c5f2ff;
    opacity: 0.25;
    stroke-opacity: 1;
}
.stroke-bg {
    stroke: #fff;
    fill: transparent;
}
.handler--first {
    fill: yellow;
}
</style>