瀏覽代碼

added (left) double-click handling and proper cursor handling when in different modes

Dimitri Korsch 3 年之前
父節點
當前提交
e813c0ae22

+ 8 - 6
webui/src/components/media/annotated-image.vue

@@ -47,6 +47,7 @@
                             :position="overlayPosition"
                             :size="image"
                             :interaction="interaction"
+                            @interaction="interaction = $event"
                             :filter="filter"
                             :label="label"
                             :video="video"
@@ -62,7 +63,7 @@
                      :labels="labels"
                      :file="current"
                      :box="infoBox"
-                     @close="infoBox=false"/>
+                     @close="interaction=defaultInteraction; infoBox=false"/>
     </template>
   </div>
 </template>
@@ -145,6 +146,7 @@ export default {
         labeledBoundingBoxes: false,
       },
       interaction: 'draw-box',
+      defaultInteraction: 'draw-box',
       filter: ['user', 'pipeline'],
       label: false,
       results: [],
@@ -190,7 +192,7 @@ export default {
   methods: {
 
     modeTooltip: function(){
-      let tip = "Selected mode";
+      let tip = "Current mode";
       switch (this.interaction) {
         case "draw-box":
           return `${tip}: draw bounding-box`;
@@ -202,16 +204,16 @@ export default {
           return `${tip}: extreme clicking`;
 
         case "move-box":
-          return `${tip}: move or resize bounding-box`;
+          return `${tip}: move or resize`;
 
         case "label-box":
-          return `${tip}: label bounding-box as "${this.label.name}"`;
+          return `${tip}: tag as "${this.label.name}"`;
 
         case "confirm-box":
-          return `${tip}: confirm bounding-box`;
+          return `${tip}: confirm`;
 
         case "remove-box":
-          return `${tip}: remove bounding-box`;
+          return `${tip}: remove`;
 
         case "info-box":
           return `${tip}: bounding-box info`;

+ 143 - 28
webui/src/components/media/annotation-box.vue

@@ -1,7 +1,14 @@
 <template>
   <div class="annotation-box"
        :style="style"
-       :class="{draggable: draggable}"
+       :class="{
+          draggable,
+          deletable,
+          taggable,
+          confirmable,
+          croppable,
+        }"
+       @contextmenu.prevent
        @mousedown.prevent="click" @touchstart.prevent="click">
     <template v-if="draggable">
       <div class="nw" @mousedown.prevent.stop="resize('nw')" @touchstart.prevent.stop="resize('nw')"/>
@@ -23,6 +30,21 @@
 <script>
 export default {
   name: "annotation-box",
+  data: function(){
+    return {
+      clickCounter: 0,
+      dblClickInterval: 200, // in ms
+
+      colors: {
+        default: '255, 255, 255',
+        pipeline: '0, 0, 255',
+        user: '255, 0, 0',
+        other_user: '136, 0, 136',
+        confirmed: '0, 180, 0',
+        other_confirmed: '180, 180, 136',
+      }
+    };
+  },
   props: [
     'box',
     'position',
@@ -48,20 +70,7 @@ export default {
       return 'unknown';
     },
     style: function () {
-      let color = '255, 255, 255';
-      if (this.box) {
-        switch (this.box.origin) {
-          case 'user':
-            color = '136, 0, 136';
-            if (this.box.origin_user === this.$root.socket.username) {
-              color = '255, 0, 0';
-            }
-            break;
-          case 'pipeline':
-            color = '0, 0, 255';
-            break;
-        }
-      }
+      let color = this.color();
 
       return {
         backgroundColor: `rgba(${color}, ${this.shine ? 0.6 : 0.3})`,
@@ -71,42 +80,128 @@ export default {
         width: this.position.w * 100 + '%',
         height: this.position.h * 100 + '%'
       }
-    }
+    },
   },
   methods: {
-    click: function (event) {
+
+    color: function() {
+      let box = this.box;
+      let current_user = this.$root.socket.username
+
+      if(!box)
+        return this.colors.default;
+
+      switch(box.origin) {
+        case 'user':
+          for (let confirmer of box.confirmations)
+            if (confirmer.confirming_user ==  current_user)
+              return this.colors.confirmed;
+
+          if (box.confirmations.length !== 0)
+            return this.colors.other_confirmed;
+
+          if (box.origin_user === current_user)
+            return this.colors.user;
+
+          return this.colors.other_user;
+
+        case 'pipeline':
+          return this.colors.pipeline;
+      }
+    },
+
+    doubleLeftClick(event) {
+      console.debug("double left click", event);
+      this.$emit('interaction', "info-box");
+      this.$emit('crop', this.box);
+    },
+
+    doubleRightClick(event) {
+      console.debug("double right click", event);
+
+    },
+
+    singleLeftClick(event) {
+      console.debug("single left click", event);
+
       if (this.deletable) {
         // TODO then / error
         this.$root.socket.post(`/results/${this.box.identifier}/remove`, {remove: true});
-        event.stopPropagation();
       }
-      if (this.draggable) {
+      else if (this.draggable) {
         // TODO then / error
         this.$emit('move', event, this.position, this.update);
-        event.stopPropagation();
       }
-      if (this.taggable) {
+      else if (this.taggable) {
         // TODO then / error
         this.$root.socket.post(`/results/${this.box.identifier}/label`, {
           label: this.taggable.identifier
         });
-        event.stopPropagation();
+        this.$emit('crop', this.box);
       }
-      if (this.confirmable) {
+      else if (this.confirmable) {
         // TODO then / error
         this.$root.socket.post(`/results/${this.box.identifier}/confirm`, {
           confirm: true
         });
-        event.stopPropagation();
       }
-      if (this.zoomable) {
+      else if (this.zoomable) {
         this.$emit('zoom', this.position);
-        event.stopPropagation();
       }
-      if (this.croppable) {
+      else if (this.croppable) {
         this.$emit('crop', this.box);
-        event.stopPropagation();
+      } else{
+        return
       }
+
+      event.stopPropagation();
+    },
+
+    singleRightClick(event) {
+      console.debug("single right click", event);
+      this.$emit('interaction', "label-box");
+      this.$emit('crop', this.box);
+
+    },
+
+    singleClick(event) {
+      if (event.which == 1){
+        this.singleLeftClick(event);
+      }
+      else if (event.which == 2){
+        console.debug("single middle click");
+      }
+      else if (event.which == 3){
+        this.singleRightClick(event);
+      }
+    },
+
+    doubleClick(event) {
+      if (event.which == 1){
+        this.doubleLeftClick(event);
+      }
+      else if (event.which == 2){
+        console.debug("double middle click");
+      }
+      else if (event.which == 3){
+        this.doubleRightClick(event);
+      }
+    },
+
+    click: function (event) {
+      this.clickCounter++;
+
+      if (this.clickCounter == 1) {
+        this.timer = setTimeout( () => {
+          this.clickCounter = 0;
+          this.singleClick(event);
+        }, this.dblClickInterval);
+        return;
+      }
+
+      clearTimeout(this.timer);
+      this.clickCounter = 0;
+      this.doubleClick(event);
     },
     resize: function (mode) {
       this.$emit('resize', mode, this.position, this.update);
@@ -216,4 +311,24 @@ export default {
   cursor: nwse-resize;
   /* background-color: brown; */
 }
+
+.draggable {
+  cursor: url('~@/assets/icons/four-directions.svg') 8 8, move;
+}
+
+.deletable {
+  cursor: url('~@/assets/icons/trash.svg') 8 8, not-allowed;
+}
+
+.taggable {
+  cursor: url('~@/assets/icons/tag.svg') 8 8, alias;
+}
+
+.confirmable {
+  cursor: url('~@/assets/icons/check.svg') 8 8, pointer;
+}
+
+.croppable {
+  cursor: url('~@/assets/icons/info.svg') 8 8, move;
+}
 </style>

+ 5 - 2
webui/src/components/media/annotation-overlay.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="annotation-overlay" :style="position"
        @touchstart="press" @touchmove="track" @touchend="release"
-       @mousedown="press" @mousemove="track" @mouseup="release"
+       @mousedown.left="press" @mousemove.left="track" @mouseup.left="release"
        @dragstart.stop>
 
     <annotation-box v-for="(box, index) in boundingBoxes"
@@ -20,6 +20,7 @@
                     @move="move"
                     @resize="resize"
                     @crop="$emit('crop', $event)"
+                    @interaction="$emit('interaction', $event)"
                     @zoom="zoomBox(index, $event)"/>
 
     <annotation-box v-if="current"
@@ -137,6 +138,7 @@ export default {
       }
     },
     press: function (event) {
+      // console.log("press", event);
       this.mousedown = true;
 
       if (this.interaction === 'draw-box') {
@@ -193,7 +195,8 @@ export default {
         this.current = this.buildRectangle(this.start, coordinates);
       }
     },
-    release: function () {
+    release: function (event) {
+      // console.log("release", event);
       this.mousedown = false;
 
       if (this.interaction === 'extreme-clicking')