Introduce ZoomThumbnail, improve zoom behavior.
--- a/src/iconolab/templates/iconolab/detail_image.html Tue Feb 21 16:04:20 2017 +0100
+++ b/src/iconolab/templates/iconolab/detail_image.html Wed Feb 22 11:04:16 2017 +0100
@@ -16,7 +16,7 @@
data-annotation-id="{{ annotation.annotation_guid }}"
data-revision-id="{{ annotation.current_revision.revision_guid }}">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
- <h4 class="list-group-item-heading">{{ annotation.current_revision.title }}</h4>
+ <h5 class="list-group-item-heading">{{ annotation.current_revision.title }}</h5>
<p class="list-group-item-text">{{ annotation.current_revision.description }}</p>
{% for tagging_info in annotation.current_revision.tagginginfo_set.all %}
<span class="label label-warning">{{ tagging_info.tag.label }}</span>
@@ -30,11 +30,12 @@
</div>
</div>
<div class="annotation-navigator-canvas">
- {% with image.media as img %}
+
<image-annotator v-bind:annotation="annotation"
ref="annotator"
- image="{{ img.url }}"></image-annotator>
- {% endwith %}
+ image="{% with image.media as img %}{{ img.url }}{% endwith %}"
+ thumbnail="{% thumbnail image.media '120x120' crop=False as thumb %}{{ thumb.url }}{% endthumbnail %}"></image-annotator>
+
<form id="form-annotation" action="{% url 'annotation_create' collection_name image.image_guid %}" method="POST">
{% csrf_token %}
<input type="hidden" name="{{ form.title.name }}">
@@ -75,7 +76,9 @@
var vm = new Vue({
el: '.annotation-navigator',
data: function() {
- return { annotation: null };
+ return {
+ annotation: null
+ };
}
});
--- a/src_js/iconolab-bundle/src/components/editor/Canvas.vue Tue Feb 21 16:04:20 2017 +0100
+++ b/src_js/iconolab-bundle/src/components/editor/Canvas.vue Wed Feb 22 11:04:16 2017 +0100
@@ -17,6 +17,18 @@
<button @click="zoomOut" type="button" class="btn btn-default"><i class="fa fa-minus" aria-hidden="true"></i></button>
</div>
</div>
+ <div class="zoom-thumbnail">
+ <zoom-thumbnail
+ ref="thumbnail"
+ @change="changeViewBox($event)"
+ @dragstart="hideTooltip"
+ @dragend="showTooltip"
+ v-bind:image="thumbnail"
+ v-bind:viewport="viewport"
+ v-bind:viewBox="viewBox"
+ v-bind:imageWidth="imageWidth"
+ v-bind:imageHeight="imageHeight"></zoom-thumbnail>
+ </div>
<div class="mode-controls">
<div class="btn-group" role="group" aria-label="...">
<button @click="mode = 'rect'" type="button" v-bind:class="{ btn: true, 'btn-default': true, 'btn-primary': mode === 'rect'}">
@@ -35,10 +47,12 @@
import Snap from 'snapsvg'
import ShapeRect from './ShapeRect.vue'
import ShapeFree from './ShapeFree.vue'
+ import ZoomThumbnail from './ZoomThumbnail.vue'
export default {
props: {
image: String,
+ thumbnail: String,
annotation: {
type: Object,
default: function () {
@@ -54,7 +68,8 @@
},
components: {
shapeRect: ShapeRect,
- shapeFree: ShapeFree
+ shapeFree: ShapeFree,
+ zoomThumbnail: ZoomThumbnail,
},
data() {
return {
@@ -105,32 +120,24 @@
factor = scale - 1;
}
- var center = this.getCenter();
-
- var viewBoxW = this.imgMinSize - (this.imgMinSize * factor);
- var viewBoxH = viewBoxW;
+ if (scale === 1) {
+ this.resetViewBox();
+ } else {
+ var center = this.getCenter();
- const viewBoxPrev = this.viewBox.slice();
- const viewBoxNew = [
- center.x - viewBoxW / 2,
- center.y - viewBoxH / 2,
- viewBoxW,
- viewBoxH
- ];
+ var viewBoxW = this.imgMinSize - (this.imgMinSize * factor);
+ var viewBoxH = viewBoxW;
- this.$refs.rect.hideTooltip();
- Snap.animate(
- viewBoxPrev, viewBoxNew,
- (viewBox) => this.paper.attr({ "viewBox": viewBox }),
- 350, mina.easeinout,
- () => {
- this.viewBox = viewBoxNew;
- this.$refs.rect.showTooltip();
- if (scale === 1) {
- this.resetZoom();
- }
- }
- );
+ var viewBox = [
+ center.x - viewBoxW / 2,
+ center.y - viewBoxH / 2,
+ viewBoxW,
+ viewBoxH
+ ];
+
+ this.hideTooltip();
+ this.animateViewBox(viewBox, () => this.showTooltip());
+ }
}
},
mounted() {
@@ -178,13 +185,30 @@
},
methods: {
-
+ hideTooltip: function() {
+ if (this.mode === 'free') {
+ this.$refs.free.hideTooltip();
+ }
+ if (this.mode === 'rect') {
+ this.$refs.rect.hideTooltip();
+ }
+ },
+ showTooltip: function() {
+ if (this.mode === 'free') {
+ this.$refs.free.showTooltip();
+ }
+ if (this.mode === 'rect') {
+ this.$refs.rect.showTooltip();
+ }
+ },
reset: function() {
// Clear shapes
this.$refs.rect.clear();
this.$refs.free.clear();
this.removeEventHandlers();
+ this.resetZoom();
+ this.resetViewBox();
if (this.mode === 'free') {
this.handleDrawFree();
@@ -228,10 +252,36 @@
}
},
+ changeViewBox: function(e) {
+ const viewBox = this.viewBox.slice();
+
+ viewBox[0] = e.x;
+ viewBox[1] = e.y;
+
+ this.paper.attr({ "viewBox": viewBox });
+ },
+
resetZoom: function() {
this.scale = 1;
- this.viewBox = [0, 0, this.imageWidth, this.imageHeight];
- this.paper.attr({ "viewBox": this.viewBox });
+ this.$refs.thumbnail.reset();
+ },
+
+ animateViewBox: function(viewBox, cb) {
+ const viewBoxPrev = this.viewBox.slice();
+
+ Snap.animate(
+ viewBoxPrev, viewBox,
+ (viewBox) => this.paper.attr({ "viewBox": viewBox }),
+ 350, mina.easeinout,
+ () => {
+ this.viewBox = viewBox;
+ if (cb) { cb(); }
+ }
+ );
+ },
+
+ resetViewBox: function() {
+ this.animateViewBox([0, 0, this.imageWidth, this.imageHeight]);
},
zoomIn: function() {
@@ -430,8 +480,8 @@
}
.zoomer {
position: absolute;
- bottom: 30px;
- right: 30px;
+ bottom: 15px;
+ right: 15px;
}
.cut-canvas {
width: 100%;
@@ -448,6 +498,11 @@
top: 15px;
left: 15px;
}
+.zoom-thumbnail {
+ position: absolute;
+ left: 15px;
+ bottom: 15px;
+}
.mode-controls .btn > svg {
margin-top: 4px;
}
--- a/src_js/iconolab-bundle/src/components/editor/ShapeRect.vue Tue Feb 21 16:04:20 2017 +0100
+++ b/src_js/iconolab-bundle/src/components/editor/ShapeRect.vue Wed Feb 22 11:04:16 2017 +0100
@@ -155,11 +155,6 @@
bottomRightHandler.drag(handlerEvents.onMove, handlerEvents.onStart, handlerEvents.onEnd);
},
- getCenter: function() {
- var shape = new Snap(this.$refs.shape);
- console.log(shape.getBBox());
- },
-
fromSVGPath: function(pathString, tooltip) {
var bBox = Snap.path.getBBox(pathString);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src_js/iconolab-bundle/src/components/editor/ZoomThumbnail.vue Wed Feb 22 11:04:16 2017 +0100
@@ -0,0 +1,134 @@
+<template>
+ <div class="wrapper">
+ <svg ref="svg" v-bind:width="thumbnailWidth" v-bind:height="thumbnailHeight">
+ <image xmlns:xlink="http://www.w3.org/1999/xlink"
+ x="0" y="0"
+ v-bind:width="thumbnailWidth" v-bind:height="thumbnailHeight"
+ v-bind:xlink:href="image"></image>
+ <rect ref="handler"
+ class="move-handler"
+ v-bind:x="x" v-bind:y="y"
+ v-bind:width="width" v-bind:height="height"
+ style="fill: black; opacity: 0.4"></rect>
+ </svg>
+ </div>
+</template>
+
+<script>
+
+ import Snap from 'snapsvg'
+
+ export default {
+ props: [
+ 'image',
+ 'viewport',
+ 'viewBox',
+ 'imageWidth',
+ 'imageHeight',
+ ],
+ data() {
+ return {
+ thumbnailWidth: 0,
+ thumbnailHeight: 0,
+ loaded: false
+ }
+ },
+ computed: {
+ x: function() {
+ return this.viewBox[0] * this.getRatioX();
+ },
+ y: function() {
+ return this.viewBox[1] * this.getRatioY();
+ },
+ width: function() {
+ return this.viewBox[2] * this.getRatioX();
+ },
+ height: function() {
+ return this.viewBox[3] * this.getRatioY();
+ }
+ },
+ methods: {
+ reset: function() {
+ var handler = new Snap(this.$refs.handler);
+ handler.node.removeAttribute('transform');
+ },
+ getRatioX: function() {
+ if (this.imageWidth === 0) { return 0; }
+
+ return this.thumbnailWidth / this.imageWidth;
+ },
+ getRatioY: function() {
+ if (this.imageHeight === 0) { return 0; }
+
+ return this.thumbnailHeight / this.imageHeight;
+ }
+ },
+ mounted() {
+
+ var svg = new Snap(this.$refs.svg);
+ var handler = new Snap(this.$refs.handler);
+
+ var img = new Image();
+ img.onload = (e) => {
+
+ var self = this;
+
+ svg.attr({
+ viewBox: [0, 0, img.width, img.height]
+ });
+
+ Object.assign(this, {
+ thumbnailWidth: img.width,
+ thumbnailHeight: img.height,
+ loaded: true
+ });
+
+ var events = {
+ onMove: function (dx, dy, x, y) {
+
+ var snapInvMatrix = this.transform().diffMatrix.invert();
+ snapInvMatrix.e = snapInvMatrix.f = 0;
+ var tdx = snapInvMatrix.x(dx, dy);
+ var tdy = snapInvMatrix.y(dx, dy);
+
+ var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [tdx, tdy];
+ this.transform(transformValue);
+
+ self.$emit('change', {
+ x: this.getBBox().x * (self.imageWidth / self.thumbnailWidth),
+ y: this.getBBox().y * (self.imageHeight / self.thumbnailHeight),
+ });
+
+ },
+ onStart: function () {
+ this.data('origTransform', this.transform().local);
+ self.$emit('dragstart');
+ },
+ onEnd: function () {
+ self.$emit('dragend');
+ }
+ }
+
+ handler.drag(events.onMove, events.onStart, events.onEnd);
+ }
+
+ img.src = this.image;
+ }
+ }
+
+</script>
+
+<style scoped>
+.wrapper {
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.wrapper svg {
+ border: 2px solid black;
+ border-radius: 4px;
+}
+.move-handler {
+ cursor: -moz-grab;
+ cursor: -webkit-grab;
+ cursor: grab;
+}
+</style>