GridEditor.vue 50.3 KB
Newer Older
Anton's avatar
Intial  
Anton committed
1 2
<template lang="pug">

3
  div.cell-grid-container(
4
  :class="{'overflow-hidden': mobileTempCell.show && cachedNewCell && !mobileTempCell.button}",
5
  style="overflow-y: hidden; scroll-behavior: smooth;")
6
    q-modal(v-model="modal", minimized, content-css="background-color: #eee; border-radius: .75rem;")
7
      grid-editor-editing-cells-mobile
8 9 10
      //
        q-btn.absolute-top-right.q-ma-sm.bg-dark(@click="handleModal()", round, size="sm", flat)
          q-icon(name="clear")
11

12 13 14 15 16 17
    // this needs to stay for adding cells on mobile for whatever reason (strangest bug ever...)
    // FIXME: find the problem and fix it
    .mobile-only.fixed-top-left.q-caption.z-max.hidden
      .bg-red {{ cachedNewCell }}
      .bg-green {{ mobileTempCell }}

18
    q-window-resize-observable(@resize="updateGridDimensions")
Anton's avatar
Intial  
Anton committed
19

christianrhansen's avatar
christianrhansen committed
20
    // ------------------------------------------------------------------------------------------------- new cell header
21 22
    #new-cell-header.fixed-top.z-top.bg-dark.text-white.transition(:class="{'show': cachedNewCell}")
      q-item.q-pl-md.q-pr-xs.q-py-none.full-height
23
        q-item-main
24
          strong New {{ newCellType }}
25
        q-item-side
26
          q-btn.bg-transparent.text-white.on-right(@click.native="clearCachedCell()", round, flat)
christianrhansen's avatar
christianrhansen committed
27
            q-icon(name="clear")
28

29 30
    // -------------------------------------------------------------------------------------------- selected cell header
    #selected-cell-header.fixed-top.z-top.bg-dark.text-white.transition(:class="{'show': showEditingCells}")
31
      q-item.q-pl-md.q-pr-xs.q-py-none.full-height(v-if="!modal")
32
        q-item-main
33
          strong {{ selectedCell.type }}
34 35 36 37
        q-item-side
          q-btn.bg-transparent.text-white.on-right(@click="event => {handleCellContextMenuDelete(event, mobileSelectedCell)}", round, flat)
            q-icon(name="delete")

38
    // ------------------------------------------------------------------------------------------------------------ grid
39
      @click="event => {addMobileCell(event)}",
40
    div.cell-grid.relative-position(
41
    v-touch-pan="panGrid",
42 43 44 45 46 47
    @dragenter="handleGridDragOver",
    @dragover="handleGridDragOver",
    @dragleave="handleGridDragEnd",
    @drop="handleGridDrop",
    @contextmenu="handleGridContextMenu",
    :style="gridStyle")
Anton's avatar
Intial  
Anton committed
48

49 50
      // context menu grid (desktop only)
      q-context-menu.desktop-only(ref="gridmenu")
Anton's avatar
Intial  
Anton committed
51 52
        q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
          q-item(
53 54 55 56 57
          v-for="action in gridContextMenuActions",
          :key="action.label",
          v-close-overlay,
          @click.native="event => {action.handler(event)}")
            q-item-main(:label="action.label")
Anton's avatar
Intial  
Anton committed
58 59 60

      template(v-if="!resizingGrid")

61
        template(v-for="(annotation, index) in annotations")
christianrhansen's avatar
christianrhansen committed
62 63

          //----- cell
64
          // v-touch-hold="event => {cellHold(event, annotation)}",
Anton's avatar
Intial  
Anton committed
65
          .cell-item(
66 67 68 69 70 71 72 73 74 75
          v-if="!annotationUIStates[annotation._uuid] || !annotationUIStates[annotation._uuid].beingDragged",
          draggable="true",
          @click.prevent="event => {handleCellTouch(event, annotation)}",
          @dragstart="event => {handleCellDragStart(event, annotation)}",
          @dragend="event => {handleCellDragEnd(event, annotation)}",
          @contextmenu="handleCellContextMenu",
          :style="getAnnotationStyle(annotation)",
          :class="getAnnotationClasses(annotation._uuid, 'cell-item')",
          :key="`cell-${index}`")

76
            //--------------------------------------------------------------------------------------- edit-/close-button
77
            // TODO: find a more elegant solution
78
            //-------------------------------------------------------------------------------------------------- desktop
79
            .desktop-only
Mathias Bär's avatar
Mathias Bär committed
80
              q-btn.edit-button.absolute-top-right(
81 82 83 84 85
              @click.prevent="event => {handleCellEditClick(event, annotation)}",
              :class="getAnnotationClasses(annotation._uuid, 'editing')",
              style="top: 8px; right: 8px;",
              :icon="annotationUIStates[annotation._uuid].editing ? 'close' : 'edit'", flat, round, size="md"
              )
86
            //--------------------------------------------------------------------------------------------------- mobile
87
            .mobile-only
88 89 90
              .edit-button.absolute.fit.bg-transparent(
              v-touch-hold="event => {handleCellInfoTouch(event, annotation)}",
              @click.prevent="event => {touchMobileCell(event, annotation)}")
91

92
                q-btn.absolute-top-right.text-dark.q-pa-none.q-mr-xs(
93
                v-if="showEditingCells",
94 95 96
                @click.prevent="event => {handleCellInfoTouch(event, annotation)}",
                flat, round, size="sm", style="margin-top: 2px;")
                  q-icon(name="edit", size="20px")
christianrhansen's avatar
christianrhansen committed
97

98 99 100 101 102
              //----- invisible edit-modal handler
              //
                q-btn.absolute-top-left.bg-blue(
                @click.prevent="event => {handleCellInfoTouch(event, annotation)}", flat, style="opacity: 0") bla
                // @click.prevent="handleModal()", flat, style="opacity: 0") bla
103

104
            //----- selecting cells disabled because it has no use currently
105 106 107 108 109 110 111 112
            // switch with cell component below to re-enable it
            //cell(
              @click.native.prevent="event => {handleCellClick(event, annotation)}", :annotation="annotation", :preview="true")
            cell(
            :annotation="annotation",
            :preview="true"
            )

christianrhansen's avatar
christianrhansen committed
113
            //----- resize-handler (desktop only)
114
            .desktop-only.cell-item-resize-handle(
115 116 117 118
            draggable="true",
            @dragstart="event => {handleCellResizerDragStart(event, annotation)}",
            @dragend="event => {handleCellResizerDragEnd(event, annotation)}",
            @dragexit="event => {handleCellResizerDragEnd(event, annotation)}")
119
              q-icon.q-ma-xs(name="network cell")
120

121 122 123
            //----- context menu for cells (desktop only)
            // TODO: needs revision
            q-context-menu.desktop-only
124 125 126 127 128 129 130
              q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
                q-item(
                v-for="action in cellContextMenuActions",
                :key="action.label",
                v-close-overlay,
                @click.native="event => {action.handler(event, annotation)}")
                  q-item-main(:label="action.label")
Anton's avatar
Intial  
Anton committed
131

132
        //----- temporary cell when pulling a new cell into grid (desktop)
133
        template(v-for="(tmpCell, index) in tmpObjects")
134
          .cell-item.cell-item-tmp(:style="getAnnotationStyle(tmpCell)", :key="`cell-tmp-${index}`")
Anton's avatar
Intial  
Anton committed
135 136
            cell(:cell="tmpCell")

christianrhansen's avatar
christianrhansen committed
137
        //-------------------------------------------------------------------------------------- temporary cell (mobile)
138
        template(v-if="mobileTempCell.show && cachedNewCell")
139

140 141 142
          .cell-item.cell-item-tmp-mobile.row.justify-center.items-center(
          ref="_mobileTempCell",
          :style="mobileTempCellStyle(mobileTempCell)")
143

christianrhansen's avatar
christianrhansen committed
144
            cell.absolute-top-left.q-ma-sm(:cell="cachedNewCell", :temp="true")
145

christianrhansen's avatar
christianrhansen committed
146
            q-btn.text-white(v-if="mobileTempCell.button",
147 148
            @click="event => {addMobileCell(event)}", round, flat, size="lg",
            style="background-color: rgba(0, 0, 0, 0);")
149
              q-icon(name="check")
150

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
        //------------------------------------------------------------------------------------------------- move handler
        //----- (main)
        cell-handler-mobile.main-handler.shadow-1(
        v-if="!cellHandler.resize.pushed",
        @onIntersectionChange="intersectionChanged",
        :element="'move'",
        v-touch-pan="handleMoveCell",
        :style="{left: cellHandler.move.x - 20 + 'px', top: cellHandler.move.y - 20 + 'px'}",
        :class="[{'pushed': !cellHandler.move.pushed}, {'hide': !mobileTempCell.onGrid && !showEditingCells}]")
          q-icon.rotate-180(name="open_with", size="22px")

        //----- (temp)
        //----- (when main move handler is outside of viewport)
        cell-handler-mobile.temp-handler.shadow-1(
        v-touch-pan="handleMoveCell",
        :doubleTap="{el: $el, type: 'scroll', oLeft: mobileTempCell.left}",
        :class="[(!mobileTempCell.onGrid || tempHandler.move.intersectingMainHandler || cellHandler.resize.pushed || cellHandler.move.pushed ? 'hide': 'show'), (tempHandler.move.side === 'left' ? 'left-side' : 'right-side')]",
        :style="{top: cellHandler.move.y + 59 - 20 + 'px'}",)
          q-icon.rotate-180(name="open_with", size="22px")

        //----------------------------------------------------------------------------------------------- resize handler
        //----- (main)
        cell-handler-mobile.main-handler.shadow-1(
        v-if="!cellHandler.move.pushed",
        @onIntersectionChange="intersectionChanged",
        :element="'resize'",
        v-touch-pan="handleResizeCell",
        :style="{left: cellHandler.resize.x - 20 + 'px', top: cellHandler.resize.y - 20 + 'px'}",
        :class="[{'pushed': !cellHandler.resize.pushed}, {'hide': !mobileTempCell.onGrid && !showEditingCells}]")
          q-icon(name="signal_cellular_4_bar", size="12px", style="margin-left: -3px;")

        //----- (temp)
        //----- (when main resize handler is outside of viewport)
        cell-handler-mobile.temp-handler.shadow-1(
        v-touch-pan="handleResizeCell",
        :class="[(!mobileTempCell.onGrid || tempHandler.resize.intersectingMainHandler || cellHandler.move.pushed || cellHandler.resize.pushed ? 'hide': 'show'), (tempHandler.resize.side === 'left' ? 'left-side' : 'right-side')]",
        :style="{top: cellHandler.resize.y + 59 - 20 + 'px'}",)
          q-icon(name="signal_cellular_4_bar", size="12px", style="margin-left: -3px;")
189

christianrhansen's avatar
christianrhansen committed
190
      // ---------------------------------------------------------------------------------------------------------------
Anton's avatar
Intial  
Anton committed
191
      template(v-else)
192
        .cell-item(:style="getAnnotationStyle({x:0,y:0,width:1,height:1})", key="cell-grid-resizer")
Anton's avatar
Intial  
Anton committed
193
          div.cell-item-resize-handle(
194 195 196 197 198
          draggable="true",
          @dragstart="event => {handleGridResizerDragStart(event)}",
          @dragend="event => {handleGridResizerDragEnd(event)}",
          @dragexit="event => {handleGridResizerDragEnd(event)}")
            q-icon(name="network cell")
Anton's avatar
Intial  
Anton committed
199

Mathias Bär's avatar
Mathias Bär committed
200
      //template(v-if="!isMobile")
201 202 203 204 205
        .fixed-top-right(style="right:18px; top:68px", v-if="!$store.state.mosys.showSources")
          q-btn(round, color="primary", small, @click="handleGridButtonClickEdit", style="margin-right: 0.5em")
            q-icon(name="add")
          q-btn(round, color="primary", small, @click="$router.push(`/mosys/grids/${$route.params.uuid}`)")
            q-icon(name="remove red eye")
Mathias Bär's avatar
Mathias Bär committed
206
      //template(v-if="isMobile")
Mathias Bär's avatar
Mathias Bär committed
207 208 209 210 211
        .fixed-top-right.q-mt-sm(v-if="!$store.state.mosys.showSources", style="z-index: 10000; padding-top: 3px;")
          q-btn.q-mr-sm(round, color="primary", size="sm", @click="handleGridButtonClickEdit")
            q-icon(name="add")
          q-btn.q-mr-md(round, color="primary", size="sm", @click="$router.push(`/mosys/grids/${$route.params.uuid}`)")
            q-icon(name="remove red eye")
Anton's avatar
Intial  
Anton committed
212

christianrhansen's avatar
christianrhansen committed
213
    // ------------------------------------------------------------------------------------------ edit box (mobile only)
214

215
    q-page-sticky.edit-box.q-mx-md.q-mb-md.transition-bottom.backdrop-filter.shadow-6.overflow-hidden.hidden(
christianrhansen's avatar
christianrhansen committed
216
    v-if="isMobile",
217
    :class="[{'show-full' : carousel.visibility && showEditingCells}, {'show-minimized' : showEditingCells}]",
218
    style="border-radius: .5rem;",
219
    position="bottom-right")
christianrhansen's avatar
christianrhansen committed
220

221
      //------------------------- buttons
222 223
      // .row.text-dark(style="background-color: rgba(0, 0, 0, .2);")
      .row.text-dark(style="background-color: rgba(255, 150, 150, .3);")
224 225 226

        //----- delete cell
        .col
christianrhansen's avatar
christianrhansen committed
227
          q-btn.q-py-none.q-pl-sm.q-ml-xs(
228
            @click="event => {handleCellContextMenuDelete(event, mobileSelectedCell)}", flat, no-ripple)
christianrhansen's avatar
christianrhansen committed
229
            q-icon(name="delete")
christianrhansen's avatar
christianrhansen committed
230

231 232
        //----- toggle edit-box
        .col.text-right
christianrhansen's avatar
christianrhansen committed
233
          q-btn.q-py-none.q-pr-sm.q-mr-xs(@click="carouselVisibility()", flat, no-ripple)
christianrhansen's avatar
christianrhansen committed
234 235
            q-icon(v-if="carousel.visibility", name="keyboard_arrow_down")
            q-icon(v-else, name="keyboard_arrow_up")
236

christianrhansen's avatar
christianrhansen committed
237
      //q-item-separator.q-ma-none
238 239 240 241 242 243

      //------------------------- carousel
      q-carousel.q-py-sm.carousel.transition-opacity(ref="carousel", v-model="slide",
      infinite, @input="getCarouselIcon(index)", style="width: 180px;")

        //----- move
christianrhansen's avatar
christianrhansen committed
244 245
        // q-carousel-slide.q-py-sm
        q-carousel-slide.q-py-none
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
          .text-center
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, 0, -1)", round, flat, size="md")
              q-icon.rotate-90(name="keyboard_backspace")
          .text-center
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, -1, 0)", round, flat, size="md")
              q-icon(name="keyboard_backspace")
            q-btn.invisible.text-dark(round, flat, size="md")
              q-icon.flip-vertical(name="photo_size_select_small")
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, 1, 0)", round, flat, size="md")
              q-icon.rotate-180(name="keyboard_backspace")
          .text-center
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, 0, 1)", round, flat, size="md")
              q-icon.rotate-270(name="keyboard_backspace")

        //----- resize
christianrhansen's avatar
christianrhansen committed
261 262
        // q-carousel-slide.q-py-sm
        q-carousel-slide.q-py-none
263 264 265 266 267 268 269 270 271 272 273 274 275
          .text-center
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, 0, -1)", round, flat, size="md")
              q-icon(name="remove")
          .text-center
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, -1, 0)", round, flat, size="md")
              q-icon(name="remove")
            q-btn.invisible.text-dark(round, flat, size="md")
              q-icon.rotate-45(name="zoom_out_map")
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, 1, 0)", round, flat, size="md")
              q-icon(name="add")
          .text-center
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, 0, 1)", round, flat, size="md")
              q-icon(name="add")
Anton's avatar
Intial  
Anton committed
276 277 278 279
</template>

<script>
  import Cell from './Cell'
280 281
  import { userHasFeature } from 'mbjs-quasar/src/lib'
  import { mapGetters } from 'vuex'
282
  import GridEditorEditingCellsMobile from './/GridEditorEditingCellsMobile'
283
  import CellHandlerMobile from './CellHandlerMobile'
Anton's avatar
Intial  
Anton committed
284 285 286 287 288 289

  const nullImage = new Image()
  nullImage.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='

  export default {
    components: {
290
      Cell,
291 292
      GridEditorEditingCellsMobile,
      CellHandlerMobile
Anton's avatar
Intial  
Anton committed
293
    },
294
    props: ['gridUuid', 'tabsAreOpen'],
Anton's avatar
Intial  
Anton committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
    data () {
      return {
        gridContextMenuActions: {
          insert_column_left: {
            label: 'Insert Column Left',
            handler: this.handleGridContextMenuInsertColumnLeft
          },
          delete_column: {
            label: 'Delete Column',
            handler: this.handleGridContextMenuDeleteColumn
          },
          insert_row_above: {
            label: 'Insert Row Above',
            handler: this.handleGridContextMenuInsertRowAbove
          },
          delete_row: {
            label: 'Delete Row',
            handler: this.handleGridContextMenuDeleteRow
          },
          edit_grid_dimensions: {
            label: 'Change Grid',
            handler: () => { this.resizingGrid = !this.resizingGrid }
          }
        },
        grid: undefined,
320
        annotations: undefined,
321
        tmpObjects: [],
322
        annotationUIStates: {},
Anton's avatar
Anton committed
323
        gridDimensions: { gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0 },
Anton's avatar
Intial  
Anton committed
324
        contextMenuClickPosition: {},
325
        resizingGrid: false,
christianrhansen's avatar
christianrhansen committed
326
        mobileSelectedCell: undefined,
327
        touch: {position: {top: undefined, left: undefined}},
328
        isMobile: this.$q.platform.is.mobile,
329 330 331 332 333 334 335 336 337 338 339 340
        mobileTempCell: {
          x: 0,
          y: 0,
          ox: 0,
          oy: 0,
          width: 1,
          height: 1,
          left: 0,
          show: false,
          onGrid: false,
          button: false
        },
341
        slide: undefined,
342
        modal: false,
343
        carousel: {visibility: false, icon: 'open_with', slide: 0},
christianrhansen's avatar
christianrhansen committed
344
        // cursor: {x: undefined, y: undefined},
345
        cellHandler: {
346
          size: {width: 40, height: 40},
347
          move: {x: 20, y: undefined, pushed: false, pos: undefined, inViewport: undefined, onLeft: undefined, onRight: undefined, dataLeft: undefined},
christianrhansen's avatar
christianrhansen committed
348
          resize: {x: undefined, y: undefined, pushed: false, pos: undefined}
349
        },
350
        tempHandler: {
christianrhansen's avatar
christianrhansen committed
351 352
          move: {left: undefined, intersectingMainHandler: false, side: undefined},
          resize: {left: undefined, intersectingMainHandler: false, side: undefined}
353 354
        },
        selectedCell: {type: undefined}
Anton's avatar
Intial  
Anton committed
355 356
      }
    },
357 358
    computed: {
      ...mapGetters({
christianrhansen's avatar
christianrhansen committed
359
        cachedNewCell: 'mosys/getNewCell',
360
        user: 'auth/getUserState',
361
        // isMobile: 'globalSettings/getIsMobile',
Mathias Bär's avatar
Mathias Bär committed
362
        // editingCells: 'mosys/getEditingCells'
363
        showEditingCells: 'mosys/getShowEditingCells',
364 365
        scrollPositionCache: 'mosys/getScrollPositionCache',
        editMode: 'mosys/getEditMode'
366
      }),
367 368 369 370 371 372
      newCellType () {
        if (this.cachedNewCell) {
          let type = this.cachedNewCell.component
          return type.substr(4, type.length - 4)
        }
      },
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
      cellContextMenuActions () {
        const actions = {
          delete: {
            label: 'Delete',
            handler: this.handleCellContextMenuDelete
          },
          insert_column_left: {
            label: 'Insert Column Left',
            handler: this.handleGridContextMenuInsertColumnLeft
          },
          insert_row_above: {
            label: 'Insert Row Above',
            handler: this.handleGridContextMenuInsertRowAbove
          }
        }
        if (userHasFeature(this.user, 'cssediting')) {
          actions.edit_css_classname = {
            label: 'Edit CSS class name',
            handler: this.handleCellContextMenuEditCSS
          }
        }
        return actions
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
      },
      gridStyle () {
        if (!this.gridDimensions || !this.gridDimensions.full) return {}
        // TODO: fix mobile grid editor view
        const cell = this.gridDimensions.full.cell
        return {
          width: `${this.gridDimensions.full.width}px`,
          height: '100%',
          'grid-auto-columns': `${cell.width}px`,
          'grid-auto-rows': `${cell.height}px`,
          'background-image': `url("data:image/svg+xml;utf8,` +
            `<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'><defs>` +
            `<pattern id='smallGrid' width='${cell.width}' height='${cell.height}' patternUnits='userSpaceOnUse'>` +
            `<path d='M ${cell.width} 0 L 0 0 0 ${cell.height}' fill='none' stroke='gray' stroke-width='0.5'/>` +
            `</pattern></defs><rect width='100%' height='100%' fill='url(%23smallGrid)' /></svg>")`
        }
411 412
      }
    },
Anton's avatar
Intial  
Anton committed
413 414
    async mounted () {
      await this.fetchData()
415
      this.resetScrollPosition()
416 417 418
    },
    beforeDestroy () {
      this.observer.disconnect()
Anton's avatar
Intial  
Anton committed
419 420
    },
    watch: {
421 422 423 424 425 426 427 428 429 430
      slide (val) {
        let icon
        switch (val) {
        case 0:
          icon = 'open_with'
          break
        case 1:
          icon = 'photo_size_select_small'
          break
        case 2:
431
          icon = 'more_horiz'
432 433 434 435 436
          break
        default:
          icon = 'open_with'
          break
        }
437 438 439
        // this.carousel.slide = val
        // this.carousel.icon = icon
        this.carousel = {slide: val, icon: icon, visibility: this.carousel.visibility}
440
      },
Anton's avatar
Anton committed
441
      annotations () {
442
        this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
443 444 445 446 447 448
      },
      gridMetadata () {
        this.updateGridDimensions()
      },
      async gridUuid () {
        await this.fetchData()
Mathias Bär's avatar
Mathias Bär committed
449 450
      },
      showEditingCells (val) {
451
        // console.log('show editing cells', val)
Mathias Bär's avatar
Mathias Bär committed
452 453 454
        if (val === false) {
          this.updateAnnotationUIStates()
        }
455 456 457
      },
      tabsAreOpen () {
        this.resetScrollPosition()
Anton's avatar
Intial  
Anton committed
458 459 460
      }
    },
    methods: {
461
      intersectionChanged (obj) {
christianrhansen's avatar
christianrhansen committed
462 463
        let _offsetLeft = Math.sign(obj.offsetLeft)

464
        // -------------------- move
465
        if (obj.element === 'move') {
christianrhansen's avatar
christianrhansen committed
466
          this.tempHandler.move.intersectingMainHandler = obj.intersecting
467

christianrhansen's avatar
christianrhansen committed
468
          // behind left side
christianrhansen's avatar
christianrhansen committed
469
          if (_offsetLeft < 0) {
470
            this.tempHandler.move.side = 'left'
471
          }
472
          // intersecting
christianrhansen's avatar
christianrhansen committed
473
          else if (isNaN(_offsetLeft)) {
christianrhansen's avatar
christianrhansen committed
474
            if (this.tempHandler.move.intersectingMainHandler && this.tempHandler.move.side === 'right') this.tempHandler.resize.intersectingMainHandler = false
475
          }
christianrhansen's avatar
christianrhansen committed
476
          // behind right side
christianrhansen's avatar
christianrhansen committed
477
          else if (_offsetLeft === 1) {
478
            this.tempHandler.move.side = 'right'
christianrhansen's avatar
christianrhansen committed
479
            this.tempHandler.resize.intersectingMainHandler = true
480
          }
481
        }
482

483
        // -------------------- resize
christianrhansen's avatar
christianrhansen committed
484
        else if (obj.element === 'resize') {
christianrhansen's avatar
christianrhansen committed
485
          this.tempHandler.resize.intersectingMainHandler = obj.intersecting
486

christianrhansen's avatar
christianrhansen committed
487
          // on left side
christianrhansen's avatar
christianrhansen committed
488
          if (_offsetLeft === -1) {
489
            this.tempHandler.resize.side = 'left'
490
          }
christianrhansen's avatar
christianrhansen committed
491
          /*
492
          // intersecting
christianrhansen's avatar
christianrhansen committed
493
          else if (isNaN(Math.sign(obj.offsetLeft))) {
494
          }
christianrhansen's avatar
christianrhansen committed
495
          */
christianrhansen's avatar
christianrhansen committed
496
          // on right side
christianrhansen's avatar
christianrhansen committed
497
          else if (_offsetLeft === 1) {
498
            this.tempHandler.resize.side = 'right'
499
          }
500 501
        }
      },
christianrhansen's avatar
christianrhansen committed
502
      handleMoveCell (obj) {
503 504
        this.cellHandler.move.x = obj.position.left
        this.cellHandler.move.y = obj.position.top
505

christianrhansen's avatar
christianrhansen committed
506 507 508 509 510
        let res = this.getGridPositionForEvent(obj)

        if (obj.isFirst) {
          this.mobileTempCell.show = true
          this.mobileTempCell.button = false
511
          this.cellHandler.move.pushed = true
christianrhansen's avatar
christianrhansen committed
512
        }
513

christianrhansen's avatar
christianrhansen committed
514 515 516
        this.mobileTempCell.x = res.x
        this.mobileTempCell.y = res.y

517 518 519
        this.mobileTempCell.ox = res.ox
        this.mobileTempCell.oy = res.oy

520 521
        this.cellHandler.move.x = res.ox
        this.cellHandler.move.y = res.oy
522

christianrhansen's avatar
christianrhansen committed
523 524 525
        if (obj.isFinal) {
          this.mobileTempCell.onGrid = true
          this.mobileTempCell.button = true
526
          this.mobileTempCell.left = this.gridDimensions.full.cell.width * (res.x - 1)
527 528 529 530
          this.cellHandler.move.pushed = false
          // this.cellHandler.move.y = this.gridDimensions.full.cell.height * (res.y - 1) + 8 + 59 + 20
          this.cellHandler.move.x = this.gridDimensions.full.cell.width * (this.mobileTempCell.x - 1) + 8 + 20
          this.cellHandler.move.y = this.gridDimensions.full.cell.height * (this.mobileTempCell.y - 1) + 8 + 20
531

532 533
          this.cellHandler.resize.x = this.gridDimensions.full.cell.width * (res.x + this.mobileTempCell.width - 1) - 8 - 20
          this.cellHandler.resize.y = this.gridDimensions.full.cell.height * (res.y + this.mobileTempCell.height - 1) - 8 - 20
christianrhansen's avatar
christianrhansen committed
534 535
        }
      },
christianrhansen's avatar
christianrhansen committed
536
      handleResizeCell (obj) {
christianrhansen's avatar
christianrhansen committed
537
        // this.cursor = {x: obj.position.left, y: obj.position.top}
538 539 540
        // this.cellHandler.resize = {x: obj.position.left, y: obj.position.top}
        this.cellHandler.resize.x = obj.position.left
        this.cellHandler.resize.y = obj.position.top
541

542 543 544 545 546 547
        // ---> panGrid
        let res = this.getGridPositionForEvent(obj)

        if (obj.isFirst) {
          this.mobileTempCell.show = true
          this.mobileTempCell.button = false
548
          this.cellHandler.resize.pushed = true
549 550 551 552 553
        }

        this.mobileTempCell.width = res.x - this.mobileTempCell.x + 1
        this.mobileTempCell.height = res.y - this.mobileTempCell.y + 1

554 555
        this.cellHandler.resize.x = res.ox
        this.cellHandler.resize.y = res.oy
556

557 558 559
        if (obj.isFinal) {
          this.mobileTempCell.onGrid = true
          this.mobileTempCell.button = true
560
          this.cellHandler.resize.pushed = false
561 562
          // this.addMobileCell(obj)
          // this.mobileTempCell.show = false
563 564
          this.cellHandler.resize.x = this.gridDimensions.full.cell.width * res.x - 8 - 20
          this.cellHandler.resize.y = this.gridDimensions.full.cell.height * res.y - 8 - 20
565 566
        }
      },
567 568 569 570
      clearCachedCell () {
        this.$store.commit('mosys/cacheNewCell', undefined)
        this.clearTempCell()
      },
571 572 573 574
      handleCellInfoTouch (event, annotation) {
        this.touchMobileCell(event, annotation)
        this.handleModal()
      },
575 576 577 578
      getCarouselIcon (val) {
        console.log(val)
      },
      carouselVisibility () {
579 580 581 582
        console.log(this.carousel.slide)
        if (this.$refs.carousel) {
          this.$refs.carousel.goToSlide(this.carousel.slide)
        }
583 584
        this.carousel.visibility = !this.carousel.visibility
      },
585 586 587
      handleModal () {
        this.modal = !this.modal
      },
588
      clearTempCell () {
589
        this.mobileTempCell = {x: undefined, y: undefined, width: undefined, height: undefined, onGrid: false, button: false}
590
      },
591
      panGrid (obj) {
592
        if (this.cachedNewCell && !this.mobileTempCell.onGrid) {
593
          // console.log('hhhhhhhhhhhhhhhhh', obj)
594 595 596 597
          let res = this.getGridPositionForEvent(obj)

          if (obj.isFirst) {
            this.mobileTempCell.show = true
598
            this.mobileTempCell.button = false
599 600 601 602 603 604 605 606
            this.mobileTempCell.x = res.x
            this.mobileTempCell.y = res.y
          }

          this.mobileTempCell.width = res.x - this.mobileTempCell.x + 1
          this.mobileTempCell.height = res.y - this.mobileTempCell.y + 1

          if (obj.isFinal) {
607
            this.mobileTempCell.onGrid = true
608
            this.mobileTempCell.button = true
609 610
            // this.addMobileCell(obj)
            // this.mobileTempCell.show = false
611 612
            this.cellHandler.move.x = this.gridDimensions.full.cell.width * (this.mobileTempCell.x - 1) + 8 + 20
            this.cellHandler.move.y = this.gridDimensions.full.cell.height * (this.mobileTempCell.y - 1) + 8 + 20
613

614 615
            this.cellHandler.resize.x = this.gridDimensions.full.cell.width * res.x - 8 - 20
            this.cellHandler.resize.y = this.gridDimensions.full.cell.height * res.y - 8 - 20
616
          }
617
        }
618
      },
619
      async cellHold (event, annotation) {
620 621
        this.$store.commit('mosys/setEditingCells', '')

622
        this.$q.notify({
623
          message: 'Copied.',
624
          color: 'dark',
625 626
          textColor: 'white',
          position: 'bottom-right'
627 628 629 630 631 632 633 634 635 636
        })

        const _cell = await this.$store.dispatch('cells/get', annotation.body.source.id)
        const resourceCell = {
          data: { content: '' },
          config: {},
          component: _cell.component
        }
        this.$store.commit('mosys/cacheNewCell', resourceCell)
      },
christianrhansen's avatar
christianrhansen committed
637 638
      moveCachedCell (obj) {
        this.touch.position = {top: obj.position.top, left: obj.position.left}
639 640 641 642 643 644 645 646 647
        /*
        if (obj.isFinal) {
          console.log(obj.isFinal)
          console.log(window.event)
          console.log(this.touch.position)
          console.log('--->->->', this.getGridPositionForEvent(event))
        }
        */
        // console.log('--->>>', this.getGridPositionForEvent(event))
christianrhansen's avatar
christianrhansen committed
648
      },
649 650
      addMobileCell (event) {
        // console.log('addMobileCell()', event, annotation)
christianrhansen's avatar
christianrhansen committed
651
        if (this.cachedNewCell) {
652
          this.handleGridDrop(event)
christianrhansen's avatar
christianrhansen committed
653
          this.$store.commit('mosys/cacheNewCell', undefined)
654 655
          this.$q.notify({
            message: 'Cell was added.',
656 657 658
            color: 'dark',
            position: 'center',
            timeout: 50
659
          })
christianrhansen's avatar
christianrhansen committed
660 661
        }
      },
662
      touchMobileCell (event, cell) {
663 664 665 666 667 668 669 670 671 672
        if (!this.mobileTempCell.onGrid) {
          Object.keys(this.annotationUIStates).filter((k) => {
            if (k === cell._uuid && this.annotationUIStates[k].editing) console.log(k, cell._uuid)
            else this.annotationUIStates[k].editing = false
          })
          this.annotationUIStates[cell._uuid].editing = !this.annotationUIStates[cell._uuid].editing
          this.updateEditingCells()
          this.$root.$emit('mosys_saveScrollPosition')
          // this.handleCellEditClick(event, annotation)
        }
673
      },
christianrhansen's avatar
christianrhansen committed
674
      handleCellTouch (event, annotation) {
675
        if (!this.cachedNewCell) {
676 677 678 679 680 681 682 683 684 685 686 687 688 689
          let
            parsed = annotation.target.selector.parse(),
            sliced = parsed.xywh.slice(0, 4),
            x = sliced[0],
            y = sliced[1],
            w = sliced[2],
            h = sliced[3]

          this.cellHandler.move.x = this.gridDimensions.full.cell.width * (x - 1) + 8 + 20
          this.cellHandler.move.y = this.gridDimensions.full.cell.height * (y - 1) + 8 + 20

          this.cellHandler.resize.x = this.gridDimensions.full.cell.width * (x + w - 1) - 8 - 20
          this.cellHandler.resize.y = this.gridDimensions.full.cell.height * (y + h - 1) - 8 - 20

690
          this.mobileSelectedCell = annotation
691
          this.getCellType(annotation)
692
        }
christianrhansen's avatar
christianrhansen committed
693
      },
694 695 696 697 698
      async getCellType (annotation) {
        let cell = await this.$store.dispatch('cells/get', annotation.body.source.id)
        let cellType = cell.component
        this.selectedCell.type = cellType.substr(4, cellType.length - 4)
      },
699
      async mobileCellMove (annotation, _x, _y) {
700 701
        this.$el.scrollLeft = this.$el.scrollLeft + (this.gridDimensions.full.cell.width * _x)

702 703
        let
          parsed = annotation.target.selector.parse(),
704
          sliced = parsed.xywh.slice(0, 4),
705
          x = sliced[0],
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
          y = sliced[1],
          w = sliced[2],
          h = sliced[3]

        // ----- x
        if (_x === -1) if (x > 1) x += _x
        if (_x === 1 && x < this.grid.config.columns - w + 1) x += _x

        // ----- y
        if (_y === -1) if (y > 1) y += _y
        if (_y === 1 && y < this.grid.config.rows - h + 1) y += _y

        // x += _x
        // y += _y

721 722 723 724 725 726 727 728
        let target = this.grid.get2DArea([x, y], parsed.xywh.slice(2))
        annotation.target.selector.value = target.selector.value
        await this.$store.dispatch('annotations/patch', [annotation.id, {
          target: {
            selector: { value: target.selector.value }
          }
        }])
      },
729
      async mobileCellResize (annotation, _w, _h) {
730 731 732
        let
          parsed = annotation.target.selector.parse(),
          [x, y, w, h] = parsed.xywh
733 734 735 736 737 738 739 740 741 742 743 744

        // ----- w
        if (_w === -1) if (w > 1) w += _w
        if (_w === 1 && x < this.grid.config.columns - w + 1) w += _w

        // ----- h
        if (_h === -1) if (h > 1) h += _h
        if (_h === 1 && y < this.grid.config.rows - h + 1) h += _h

        // w += _w
        // h += _h

745 746 747 748 749 750 751 752
        const value = { xywh: [x, y, w, h] }
        annotation.target.selector.value = value

        await this.$store.dispatch('annotations/patch', [annotation.id, { target: { selector: { value } } }])
      },
      setEditMode (mode) {
        this.$store.commit('mosys/setEditMode', mode)
      },
753 754 755 756
      //
      // DATA
      //

Anton's avatar
Intial  
Anton committed
757 758 759
      async fetchData () {
        if (this.gridUuid) {
          this.grid = await this.$store.dispatch('maps/get', this.gridUuid)
760 761 762 763 764 765 766 767
          if (!Object.keys(this.grid.config).length) {
            this.grid.config = {
              columns: 10,
              rows: 6,
              ratio: 16 / 9.0
            }
            await this.updateGridMetadataStore()
          }
Anton's avatar
Intial  
Anton committed
768
          this.updateGridDimensions()
769 770 771 772 773 774
          const { items } = await this.$store.dispatch('annotations/find', {
            'target.id': this.grid.id,
            'body.purpose': 'linking',
            'body.type': 'Cell'
          })
          this.annotations = items
Anton's avatar
Intial  
Anton committed
775 776
        }
      },
777 778 779
      async updateGridMetadataStore () {
        await this.$store.dispatch('maps/patch', [this.grid.id, { config: this.grid.config }])
        this.updateGridDimensions()
Anton's avatar
Intial  
Anton committed
780
      },
781 782 783 784 785
      updateSelectedCells () {
        const _this = this
        let selectedCells = Object.keys(this.annotationUIStates).filter(k => {
          return _this.annotationUIStates[k].selected
        }).map(k => {
Christian Hansen's avatar
Christian Hansen committed
786 787
          // return _this.cells.find(c => c._uuid === k)
          return _this.annotations.find(c => c._uuid === k)
788 789
        })
        this.$store.commit('mosys/setSelectedCells', selectedCells)
Anton's avatar
Intial  
Anton committed
790
      },
Mathias Bär's avatar
Mathias Bär committed
791 792 793 794 795 796 797 798
      updateEditingCells () {
        const _this = this
        let editingCells = Object.keys(this.annotationUIStates).filter(k => {
          return _this.annotationUIStates[k].editing
        }).map(k => {
          // return _this.cells.find(c => c._uuid === k)
          return _this.annotations.find(c => c._uuid === k)
        })
799 800 801 802 803 804 805
        /*
        console.log('this.annotationUIStates: ', this.annotationUIStates)
        console.log('editingCells: ', editingCells)
        if (this.isMobile) {
          console.log('MOBILE')
        }
        */
Mathias Bär's avatar
Mathias Bär committed
806 807
        this.$store.commit('mosys/setEditingCells', editingCells)
      },
808 809 810 811 812
      updateAnnotationUIStates () {
        let newAnnotationUIStates = {}
        this.annotations.forEach(a => {
          newAnnotationUIStates[a._uuid] = {
            selected: false,
Mathias Bär's avatar
Mathias Bär committed
813
            editing: false,
814
            beingResized: false,
Mathias Bär's avatar
Mathias Bär committed
815
            beginDragged: false,
816
            annotation: a
Anton's avatar
Intial  
Anton committed
817
          }
818 819
        })
        this.annotationUIStates = newAnnotationUIStates
Anton's avatar
Intial  
Anton committed
820
        this.updateSelectedCells()
Mathias Bär's avatar
Mathias Bär committed
821
        this.updateEditingCells()
822
      },
823 824 825 826 827

      //
      // GRID DRAG & DROP HANDLERS
      //

828
      async handleGridDragOver (event) {
Anton's avatar
Intial  
Anton committed
829 830 831
        let _this = this
        if (this.resizingGrid) {
          const position = this.getGridPositionForEvent(event)
832 833
          this.grid.config.ratio = position.ox / (position.oy * 1.0)
          await this.updateGridMetadataStore()
Anton's avatar
Intial  
Anton committed
834 835
        }
        else {
836 837
          let annotation = this.annotations.filter(annotation => {
            if (!_this.annotationUIStates[annotation._uuid]) return false
Mathias Bär's avatar
Mathias Bär committed
838
            return _this.annotationUIStates[annotation._uuid].beingDragged ||
839
              _this.annotationUIStates[annotation._uuid].beingResized
Anton's avatar
Intial  
Anton committed
840 841
          }).shift()
          let offset, position
842 843 844 845
          if (!annotation) {
            annotation = {
              target: this.grid.get2DArea([1, 1], [1, 1])
            }
Anton's avatar
Intial  
Anton committed
846 847 848
            position = this.getGridPositionForEvent(event)
          }
          else {
849
            offset = this.annotationUIStates[annotation._uuid].draggingOffset
Anton's avatar
Intial  
Anton committed
850 851
            position = this.getGridPositionForEvent(event, offset)
          }
852 853
          if (!this.tmpObjects.length) this.tmpObjects.push(annotation)
          const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
854
          if (event.dataTransfer.types.includes('text/plain')) {
855 856
            parsed.xywh[0] = position.x
            parsed.xywh[1] = position.y
Anton's avatar
Intial  
Anton committed
857 858 859
            event.preventDefault()
          }
          else {
860 861
            parsed.xywh[2] = Math.max(1, 1 + position.x - parsed.xywh[0])
            parsed.xywh[3] = Math.max(1, 1 + position.y - parsed.xywh[1])
Anton's avatar
Intial  
Anton committed
862
          }
863
          annotation.target.selector.value = parsed
Anton's avatar
Intial  
Anton committed
864 865 866
        }
      },
      handleGridDragEnd () {
867
        this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
868
      },
869
      async handleGridDrop (event) {
870 871 872 873 874
        let dropData
        if (event.dataTransfer) dropData = event.dataTransfer.getData('text/plain')
        if (!dropData) dropData = JSON.stringify(this.cachedNewCell)
        // console.log('handleGridDrop - event', event)
        // console.log('handleGridDrop - dropData', dropData)
875 876
        if (dropData) {
          dropData = JSON.parse(dropData)
877
          let annotation = this.annotations.find(a => a.id === dropData.id)
878 879 880 881 882
          const { x, y } = this.getGridPositionForEvent(
            event,
            annotation ? this.annotationUIStates[annotation._uuid].draggingOffset : undefined
          )
          if (annotation) {
883 884 885
            const
              parsed = annotation.target.selector.parse(),
              target = this.grid.get2DArea([x, y], parsed.xywh.slice(2))
886 887 888 889 890 891
            annotation.target.selector.value = target.selector.value
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: {
                selector: { value: target.selector.value }
              }
            }])
Anton's avatar
Intial  
Anton committed
892 893
          }
          else {
894 895
            if (this.mobileTempCell.x) console.log('this.mobileTempCell', this.mobileTempCell)
            let test = this.grid.get2DArea([this.mobileTempCell.x, this.mobileTempCell.y], [this.mobileTempCell.width, this.mobileTempCell.height])
896 897 898 899 900 901 902 903 904 905 906
            const
              { data, config, component } = dropData,
              cell = await this.$store.dispatch('cells/post', { data, config, component })
            annotation = await this.$store.dispatch('annotations/post', {
              body: {
                type: 'Cell',
                purpose: 'linking',
                source: {
                  id: cell.id
                }
              },
907 908
              // target: this.grid.get2DArea([x, y], [1, 1])
              target: test
909
            })
910
            this.annotations.push(annotation)
Anton's avatar
Anton committed
911
            this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
912
          }
913

914
          this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
915 916
          event.preventDefault()
        }
917
        this.clearTempCell()
Anton's avatar
Intial  
Anton committed
918
      },
919 920 921 922

      //
      // CELL DRAG & DROP HANDLERS
      //
Mathias Bär's avatar
Mathias Bär committed
923 924 925
      handleCellEditClick (event, cell) {
        this.annotationUIStates[cell._uuid].editing = !this.annotationUIStates[cell._uuid].editing
        this.updateEditingCells()
926
        this.$root.$emit('mosys_saveScrollPosition')
Mathias Bär's avatar
Mathias Bär committed
927
      },
928 929 930 931 932
      handleCellClick (event, cell) {
        this.annotationUIStates[cell._uuid].selected = !this.annotationUIStates[cell._uuid].selected
        this.updateSelectedCells()
      },
      handleCellDragStart (event, annotation) {
christianrhansen's avatar
christianrhansen committed
933
        console.log('§§§§§§§§', annotation)
934 935 936 937 938 939 940 941 942 943 944 945
        if (!this.isMobile) {
          if (!this.annotationUIStates[annotation._uuid].beingResized) {
            event.dataTransfer.setData('text/plain', JSON.stringify(annotation))
            event.dataTransfer.setDragImage(nullImage, 0, 0)
            let elContainerBoundingBox = this.$el.getBoundingClientRect()
            let elBoundingBox = event.target.getBoundingClientRect()
            let offset = {
              x: (event.clientX - elContainerBoundingBox.x) - (elBoundingBox.x - elContainerBoundingBox.x),
              y: (event.clientY - elContainerBoundingBox.y) - (elBoundingBox.y - elContainerBoundingBox.y)
            }
            this.annotationUIStates[annotation._uuid].draggingOffset = offset
            this.annotationUIStates[annotation._uuid].beingDragged = true
946
          }
947
          this.tmpObjects.push(annotation)
948 949 950
        }
      },
      handleCellDragEnd (event, annotation) {
951 952 953
        if (!this.mobile) {
          this.annotationUIStates[annotation._uuid].beingDragged = false
        }
954 955 956 957
      },
      async handleCellContextMenuDelete (event, annotation) {
        this.annotationUIStates[annotation._uuid].selected = false
        this.updateSelectedCells()
Mathias Bär's avatar
Mathias Bär committed
958
        this.updateEditingCells()
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
        await this.$store.dispatch('cells/delete', annotation.body.source.id)
        await this.$store.dispatch('annotations/delete', annotation.id)
        this.annotations = this.annotations.filter(a => a.id !== annotation.id)
      },

      //
      // GRID RESIZE HANDLERS

      handleGridResizerDragStart (event) {
        event.dataTransfer.setDragImage(nullImage, 0, 0)
      },
      async handleGridResizerDragEnd () {
        await this.updateGridMetadataStore()
      },

      //
      // CELL RESIZE HANDLERS
      //

      handleCellResizerDragStart (event, annotation) {
        event.dataTransfer.setDragImage(nullImage, 0, 0)
        this.annotationUIStates[annotation._uuid].beingResized = true
        this.tmpObjects.push(annotation)
      },
      async handleCellResizerDragEnd (event, annotation) {
        let position = this.getGridPositionForEvent(event)
        let
          parsed = annotation.target.selector.parse(),
          [x, y, w, h] = parsed.xywh
        w = Math.max(1, 1 + position.x - x)
        h = Math.max(1, 1 + position.y - y)
        const value = { xywh: [x, y, w, h] }
        annotation.target.selector.value = value
        this.annotationUIStates[annotation._uuid].beingResized = false
        this.tmpObjects = []
        await this.$store.dispatch('annotations/patch', [annotation.id, { target: { selector: { value } } }])
      },

      //
      // CELL CONTEXT MENU
      //

      handleCellContextMenu (event) {
Anton's avatar
Intial  
Anton committed
1002 1003
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
      },
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
      async handleCellContextMenuEditCSS (event, annotation) {
        try {
          let styleClass = await this.$q.dialog({
            title: this.$t('forms.edit_css_class.title'),
            ok: this.$t('buttons.set_css_class'),
            cancel: this.$t('buttons.cancel'),
            prompt: {
              model: annotation.styleClass,
              type: 'text'
            }
          })
          if (styleClass) {
            if (!styleClass.length) styleClass = undefined
            else if (styleClass.indexOf('.') === 0) styleClass = styleClass.substr(1)
          }
          annotation.styleClass = styleClass
          this.$store.dispatch('annotations/patch', [annotation._uuid, { target: { styleClass: styleClass || null } }])
Anton's avatar
Intial  
Anton committed
1021
        }
1022 1023 1024 1025 1026 1027 1028 1029 1030
        catch (e) { /* dialog canceled */ }
      },

      //
      // GRID CONTEXT MENU
      //

      handleGridContextMenu (event) {
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
Anton's avatar
Intial  
Anton committed
1031 1032 1033
      },
      async handleGridContextMenuInsertColumnLeft () {
        let position = this.contextMenuClickPosition
1034 1035 1036 1037 1038 1039 1040 1041
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[0] >= position.x) {
            parsed.xywh[0] += 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
1042 1043
          }
        }
1044
        this.grid.config.columns += 1
Anton's avatar
Intial  
Anton committed
1045 1046 1047 1048
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteColumn () {
        let position = this.contextMenuClickPosition
1049 1050 1051 1052 1053 1054 1055 1056
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[0] > position.x) {
            parsed.xywh[0] -= 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
1057 1058
          }
        }
1059
        this.grid.config.columns -= 1
Anton's avatar
Intial  
Anton committed
1060 1061 1062 1063
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuInsertRowAbove () {
        let position = this.contextMenuClickPosition
1064 1065 1066 1067 1068 1069 1070 1071
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[1] >= position.y) {
            parsed.xywh[1] += 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
1072 1073
          }
        }
1074
        this.grid.config.rows += 1
Anton's avatar
Intial  
Anton committed
1075 1076 1077 1078
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteRow () {
        let position = this.contextMenuClickPosition
1079 1080 1081 1082 1083 1084 1085 1086
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[1] > position.y) {
            parsed.xywh[1] -= 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
1087 1088
          }
        }
1089
        this.grid.config.rows -= 1
Anton's avatar
Intial  
Anton committed
1090 1091
        await this.updateGridMetadataStore()
      },
1092 1093 1094 1095 1096

      //
      // NAVIGATION
      //

Anton's avatar
Intial  
Anton committed
1097
      handleGridButtonClickEdit () {
1098
        this.$store.commit('mosys/toggleSources')
Anton's avatar
Intial  
Anton committed
1099
      },
1100 1101 1102 1103 1104

      //
      // GRID HELPERS
      //

Anton's avatar
Anton committed
1105
      getGridPositionForEvent (event, offset = { x: 0, y: 0 }) {
1106
        // console.log('###', event)
Anton's avatar
Anton committed
1107
        offset = { x: 0, y: 0 } // TODO: remove quick fix
1108 1109 1110 1111 1112 1113 1114

        let _clientX, _clientY

        if (event.clientX && event.clientY) {
          _clientX = event.clientX
          _clientY = event.clientY
        }
1115
        if (event.position) {
1116 1117 1118 1119
          _clientX = event.position.left
          _clientY = event.