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

3
  div.cell-grid-container(
4
  :class="{'overflow-hidden': activeHandler}",
5
  style="overflow-y: hidden; scroll-behavior: smooth;")
christianrhansen's avatar
christianrhansen committed
6

christianrhansen's avatar
christianrhansen committed
7 8 9
    q-window-resize-observable(@resize="updateGridDimensions")

    //----- modal for existing cell editing
christianrhansen's avatar
christianrhansen committed
10
    q-modal(v-model="modal", minimized, content-css="background-color: #eee; border-radius: .5rem;")
11
      grid-editor-editing-cells-mobile
12

13
    // -----------------------------------------------------------------------------------------------------------------
christianrhansen's avatar
christianrhansen committed
14
    // ----------------------------------------------------------------------------------------------- "new cell"-header
15 16
    #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
17
        q-item-main
christianrhansen's avatar
christianrhansen committed
18
          strong New {{ cachedNewCellType }}
19

christianrhansen's avatar
christianrhansen committed
20 21
        q-item-side.text-white
          q-btn(@click="event => {addMobileCell(event)}", round, flat)
22 23
            q-icon(name="check")

christianrhansen's avatar
christianrhansen committed
24
          q-btn.clear-button.q-px-md.q-mr-md.on-right(@click="clearHandler('cached cell')", flat)
christianrhansen's avatar
christianrhansen committed
25
            q-icon(name="clear")
26

christianrhansen's avatar
christianrhansen committed
27
    // ------------------------------------------------------------------------------------------ "selected cell"-header
28
    #selected-cell-header.fixed-top.z-top.bg-dark.text-white.transition(:class="{'show': showEditingCells}")
29
      q-item.q-pl-md.q-pr-none.q-py-none.full-height(v-if="!modal")
30
        q-item-main
31
          strong {{ selectedCell.type }}
32

christianrhansen's avatar
christianrhansen committed
33 34
        q-item-side.text-white
          q-btn.q-px-sm(@click="event => {handleCellEdit(event, selectedCell.annotation)}", flat)
35 36
            q-icon(name="edit")

christianrhansen's avatar
christianrhansen committed
37
          q-btn.q-px-sm.on-right.on-left(
38
          @click="event => {handleCellContextMenuDelete(event, selectedCell.annotation)}", flat)
39
            q-icon(name="delete")
40

christianrhansen's avatar
christianrhansen committed
41
          q-btn.clear-button.q-px-md.q-mr-md.on-right(@click="clearHandler('cell')", flat)
42 43
            q-icon(name="clear")

44
    // -----------------------------------------------------------------------------------------------------------------
45
    // ------------------------------------------------------------------------------------------------------------ grid
46
    div.cell-grid.relative-position(
47
    v-touch-pan="panGrid",
48 49 50 51 52 53
    @dragenter="handleGridDragOver",
    @dragover="handleGridDragOver",
    @dragleave="handleGridDragEnd",
    @drop="handleGridDrop",
    @contextmenu="handleGridContextMenu",
    :style="gridStyle")
Anton's avatar
Intial  
Anton committed
54

55
      //----------------------------------------------------------------------------------- context menu: grid (desktop)
56
      q-context-menu.desktop-only(ref="gridmenu")
Anton's avatar
Intial  
Anton committed
57 58
        q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
          q-item(
59 60 61 62 63
          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
64 65 66

      template(v-if="!resizingGrid")

christianrhansen's avatar
christianrhansen committed
67 68
        //--------------------------------------------------------------------------------------------------------------
        //--------------------------------------------------------------------------------------------------------- cell
69
        template(v-for="(annotation, index) in annotations")
70
          // v-touch-hold="event => {cellCopy(event, annotation)}",
Anton's avatar
Intial  
Anton committed
71
          .cell-item(
72 73 74 75 76 77 78 79 80 81
          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}`")

82
            //------------------------------------------------------------------------------------ edit-button (desktop)
83
            .desktop-only
Mathias Bär's avatar
Mathias Bär committed
84
              q-btn.edit-button.absolute-top-right(
85 86 87
              @click.prevent="event => {handleCellEditClick(event, annotation)}",
              :class="getAnnotationClasses(annotation._uuid, 'editing')",
              style="top: 8px; right: 8px;",
christianrhansen's avatar
christianrhansen committed
88 89
              :icon="annotationUIStates[annotation._uuid].editing ? 'close' : 'edit'", flat, round, size="md")

90
            //------------------------------------------------------------------------------------- select cell (mobile)
91
            .mobile-only
92
              .edit-button.absolute.fit.bg-transparent(
93
              v-touch-hold="event => {handleCellEdit(event, annotation)}",
94
              @click.prevent="event => {touchMobileCell(event, annotation)}")
95

96
            //--------------------------------------------------------------------------------------------- cell content
97
            //----- selecting cells disabled because it has no use currently
98 99 100 101 102
            // switch with cell component below to re-enable it
            //cell(
              @click.native.prevent="event => {handleCellClick(event, annotation)}", :annotation="annotation", :preview="true")
            cell(
            :annotation="annotation",
christianrhansen's avatar
christianrhansen committed
103
            :preview="true")
104

105 106
            //-------------------------------------------------------------------------------------------------- desktop
            //------------------------------------------------------------------------------------------- resize-handler
107
            .desktop-only.cell-item-resize-handle(
108 109 110 111
            draggable="true",
            @dragstart="event => {handleCellResizerDragStart(event, annotation)}",
            @dragend="event => {handleCellResizerDragEnd(event, annotation)}",
            @dragexit="event => {handleCellResizerDragEnd(event, annotation)}")
112
              q-icon.q-ma-xs(name="network cell")
113

114
            //-------------------------------------------------------------------------------------- context menu: cells
115 116
            // TODO: needs revision
            q-context-menu.desktop-only
117 118 119 120 121 122 123
              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
124

christianrhansen's avatar
christianrhansen committed
125 126
        //----------------------------------------------------------------------------------------------- temporary cell
        //------------------------------------------------------------------------------------------------------ desktop
127
        template(v-for="(tmpCell, index) in tmpObjects")
128
          .cell-item.cell-item-tmp(:style="getAnnotationStyle(tmpCell)", :key="`cell-tmp-${index}`")
Anton's avatar
Intial  
Anton committed
129 130
            cell(:cell="tmpCell")

christianrhansen's avatar
christianrhansen committed
131
        //------------------------------------------------------------------------------------------------------- mobile
132
        template(v-if="mobileTempCell.show && cachedNewCell")
133
          .cell-item.cell-item-tmp-mobile(:style="mobileTempCellStyle(mobileTempCell)")
134

christianrhansen's avatar
christianrhansen committed
135 136
        //------------------------------------------------------------------------------------------------------ handler
        //--------------------------------------------------------------------------------------------------------- move
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
        //----- (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")

christianrhansen's avatar
christianrhansen committed
156
        //------------------------------------------------------------------------------------------------------- resize
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
        //----- (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;")
174

christianrhansen's avatar
christianrhansen committed
175
      // ---------------------------------------------------------------------------------------------------------------
Anton's avatar
Intial  
Anton committed
176
      template(v-else)
177
        .cell-item(:style="getAnnotationStyle({x:0,y:0,width:1,height:1})", key="cell-grid-resizer")
Anton's avatar
Intial  
Anton committed
178
          div.cell-item-resize-handle(
179 180 181 182 183
          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
184

Mathias Bär's avatar
Mathias Bär committed
185
      //template(v-if="!isMobile")
186 187 188 189 190
        .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
191
      //template(v-if="isMobile")
Mathias Bär's avatar
Mathias Bär committed
192 193 194 195 196
        .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
197 198 199 200 201

</template>

<script>
  import Cell from './Cell'
202 203
  import { userHasFeature } from 'mbjs-quasar/src/lib'
  import { mapGetters } from 'vuex'
204
  import GridEditorEditingCellsMobile from './/GridEditorEditingCellsMobile'
205
  import CellHandlerMobile from './CellHandlerMobile'
Anton's avatar
Intial  
Anton committed
206 207 208 209 210 211

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

  export default {
    components: {
212
      Cell,
213 214
      GridEditorEditingCellsMobile,
      CellHandlerMobile
Anton's avatar
Intial  
Anton committed
215
    },
216
    props: ['gridUuid', 'tabsAreOpen'],
Anton's avatar
Intial  
Anton committed
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    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,
242
        annotations: undefined,
243
        tmpObjects: [],
244
        annotationUIStates: {},
Anton's avatar
Anton committed
245
        gridDimensions: { gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0 },
Anton's avatar
Intial  
Anton committed
246
        contextMenuClickPosition: {},
247
        resizingGrid: false,
248
        isMobile: this.$q.platform.is.mobile,
249 250 251 252 253 254 255 256 257 258 259 260
        mobileTempCell: {
          x: 0,
          y: 0,
          ox: 0,
          oy: 0,
          width: 1,
          height: 1,
          left: 0,
          show: false,
          onGrid: false,
          button: false
        },
261
        modal: false,
262
        cellHandler: {
263
          size: {width: 40, height: 40},
264
          move: {x: 20, y: undefined, gridPosition: {row: undefined, column: undefined}, pushed: false, pos: undefined, inViewport: undefined, onLeft: undefined, onRight: undefined, dataLeft: undefined},
265
          resize: {x: undefined, y: undefined, gridPosition: {row: undefined, column: undefined}, pushed: false, pos: undefined}
266
        },
267
        tempHandler: {
christianrhansen's avatar
christianrhansen committed
268 269
          move: {left: undefined, intersectingMainHandler: false, side: undefined},
          resize: {left: undefined, intersectingMainHandler: false, side: undefined}
270
        },
271
        activeHandler: false,
272
        selectedCell: {type: undefined, annotation: undefined}
Anton's avatar
Intial  
Anton committed
273 274
      }
    },
275 276
    computed: {
      ...mapGetters({
christianrhansen's avatar
christianrhansen committed
277
        cachedNewCell: 'mosys/getNewCell',
278
        user: 'auth/getUserState',
279
        // isMobile: 'globalSettings/getIsMobile',
Mathias Bär's avatar
Mathias Bär committed
280
        // editingCells: 'mosys/getEditingCells'
281
        showEditingCells: 'mosys/getShowEditingCells',
282 283
        scrollPositionCache: 'mosys/getScrollPositionCache',
        editMode: 'mosys/getEditMode'
284
      }),
christianrhansen's avatar
christianrhansen committed
285
      cachedNewCellType () {
286 287 288 289 290
        if (this.cachedNewCell) {
          let type = this.cachedNewCell.component
          return type.substr(4, type.length - 4)
        }
      },
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
      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
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
      },
      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>")`
        }
329 330
      }
    },
Anton's avatar
Intial  
Anton committed
331 332
    async mounted () {
      await this.fetchData()
333
      this.resetScrollPosition()
334 335 336
    },
    beforeDestroy () {
      this.observer.disconnect()
Anton's avatar
Intial  
Anton committed
337 338
    },
    watch: {
339
      'cellHandler.move.gridPosition': {
340 341
        handler: function (obj) {
          if (this.showEditingCells) {
342
            this.mobileCellMove(this.selectedCell.annotation, obj.column, obj.row)
343
          }
344 345 346
        },
        deep: true
      },
347 348 349
      'cellHandler.resize.gridPosition': {
        handler: function (obj) {
          if (this.showEditingCells) {
350 351
            let w = obj.column - this.cellHandler.move.gridPosition.column + 1
            let h = obj.row - this.cellHandler.move.gridPosition.row + 1
352
            this.mobileCellResize(this.selectedCell.annotation, w, h)
353 354 355 356
          }
        },
        deep: true
      },
Anton's avatar
Anton committed
357
      annotations () {
358
        this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
359 360 361 362 363 364
      },
      gridMetadata () {
        this.updateGridDimensions()
      },
      async gridUuid () {
        await this.fetchData()
Mathias Bär's avatar
Mathias Bär committed
365 366
      },
      showEditingCells (val) {
367
        // console.log('show editing cells', val)
Mathias Bär's avatar
Mathias Bär committed
368 369 370
        if (val === false) {
          this.updateAnnotationUIStates()
        }
371 372 373
      },
      tabsAreOpen () {
        this.resetScrollPosition()
Anton's avatar
Intial  
Anton committed
374 375 376
      }
    },
    methods: {
377 378 379 380 381 382 383 384
      clearHandler (target) {
        switch (target) {
        case 'cell':
          Object.keys(this.annotationUIStates).filter((k) => {
            this.annotationUIStates[k].editing = false
          })
          this.$store.commit('mosys/setEditingCells', '')
          break
385 386
        case 'cached cell':
          this.$store.commit('mosys/cacheNewCell', undefined)
387 388 389
          this.clearHandler('temp cell')
          break
        case 'temp cell':
390
          this.mobileTempCell = {x: undefined, y: undefined, width: undefined, height: undefined, onGrid: false}
391
          break
392 393
        }
      },
394
      intersectionChanged (obj) {
christianrhansen's avatar
christianrhansen committed
395 396
        let _offsetLeft = Math.sign(obj.offsetLeft)

397
        // -------------------- move
398
        if (obj.element === 'move') {
christianrhansen's avatar
christianrhansen committed
399
          this.tempHandler.move.intersectingMainHandler = obj.intersecting
400

christianrhansen's avatar
christianrhansen committed
401
          // behind left side
christianrhansen's avatar
christianrhansen committed
402
          if (_offsetLeft < 0) {
403
            this.tempHandler.move.side = 'left'
404
          }
405
          // intersecting
christianrhansen's avatar
christianrhansen committed
406
          else if (isNaN(_offsetLeft)) {
christianrhansen's avatar
christianrhansen committed
407
            if (this.tempHandler.move.intersectingMainHandler && this.tempHandler.move.side === 'right') this.tempHandler.resize.intersectingMainHandler = false
408
          }
christianrhansen's avatar
christianrhansen committed
409
          // behind right side
christianrhansen's avatar
christianrhansen committed
410
          else if (_offsetLeft === 1) {
411
            this.tempHandler.move.side = 'right'
christianrhansen's avatar
christianrhansen committed
412
            this.tempHandler.resize.intersectingMainHandler = true
413
          }
414
        }
415

416
        // -------------------- resize
christianrhansen's avatar
christianrhansen committed
417
        else if (obj.element === 'resize') {
christianrhansen's avatar
christianrhansen committed
418
          this.tempHandler.resize.intersectingMainHandler = obj.intersecting
419

christianrhansen's avatar
christianrhansen committed
420
          // on left side
christianrhansen's avatar
christianrhansen committed
421
          if (_offsetLeft === -1) {
422
            this.tempHandler.resize.side = 'left'
423
          }
christianrhansen's avatar
christianrhansen committed
424
          /*
425
          // intersecting
christianrhansen's avatar
christianrhansen committed
426
          else if (isNaN(Math.sign(obj.offsetLeft))) {
427
          }
christianrhansen's avatar
christianrhansen committed
428
          */
christianrhansen's avatar
christianrhansen committed
429
          // on right side
christianrhansen's avatar
christianrhansen committed
430
          else if (_offsetLeft === 1) {
431
            this.tempHandler.resize.side = 'right'
432
          }
433 434
        }
      },
christianrhansen's avatar
christianrhansen committed
435
      handleMoveCell (obj) {
436 437
        this.cellHandler.move.x = obj.position.left
        this.cellHandler.move.y = obj.position.top
438

christianrhansen's avatar
christianrhansen committed
439 440
        let res = this.getGridPositionForEvent(obj)

441 442 443
        this.cellHandler.move.gridPosition.row = res.y
        this.cellHandler.move.gridPosition.column = res.x

christianrhansen's avatar
christianrhansen committed
444
        if (obj.isFirst) {
445
          this.activeHandler = true
christianrhansen's avatar
christianrhansen committed
446
          this.mobileTempCell.show = true
447
          this.cellHandler.move.pushed = true
christianrhansen's avatar
christianrhansen committed
448
        }
449

christianrhansen's avatar
christianrhansen committed
450 451 452
        this.mobileTempCell.x = res.x
        this.mobileTempCell.y = res.y

453 454 455
        this.mobileTempCell.ox = res.ox
        this.mobileTempCell.oy = res.oy

456 457
        this.cellHandler.move.x = res.ox
        this.cellHandler.move.y = res.oy
458

christianrhansen's avatar
christianrhansen committed
459
        if (obj.isFinal) {
460
          this.activeHandler = false
christianrhansen's avatar
christianrhansen committed
461
          this.mobileTempCell.onGrid = true
462
          this.mobileTempCell.left = this.gridDimensions.full.cell.width * (res.x - 1)
463 464 465 466
          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
467

468 469
          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
470 471
        }
      },
christianrhansen's avatar
christianrhansen committed
472
      handleResizeCell (obj) {
christianrhansen's avatar
christianrhansen committed
473
        // this.cursor = {x: obj.position.left, y: obj.position.top}
474 475 476
        // 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
477

478 479 480
        // ---> panGrid
        let res = this.getGridPositionForEvent(obj)

481 482 483
        this.cellHandler.resize.gridPosition.row = res.y
        this.cellHandler.resize.gridPosition.column = res.x

484
        if (obj.isFirst) {
485
          this.activeHandler = true
486
          this.mobileTempCell.show = true
487
          this.cellHandler.resize.pushed = true
488 489 490 491 492
        }

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

493 494
        this.cellHandler.resize.x = res.ox
        this.cellHandler.resize.y = res.oy
495

496
        if (obj.isFinal) {
497
          this.activeHandler = false
498
          this.mobileTempCell.onGrid = true
499
          this.cellHandler.resize.pushed = false
500 501
          // this.addMobileCell(obj)
          // this.mobileTempCell.show = false
502 503
          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
504 505
        }
      },
506 507
      handleCellEdit (event, annotation) {
        console.log('handleCellEdit', annotation)
508 509 510
        this.touchMobileCell(event, annotation)
        this.handleModal()
      },
511 512 513
      handleModal () {
        this.modal = !this.modal
      },
514
      panGrid (obj) {
515
        if (this.cachedNewCell && !this.mobileTempCell.onGrid) {
516
          // console.log('hhhhhhhhhhhhhhhhh', obj)
517 518 519 520 521 522 523 524 525 526 527 528
          let res = this.getGridPositionForEvent(obj)

          if (obj.isFirst) {
            this.mobileTempCell.show = true
            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) {
529
            this.mobileTempCell.onGrid = true
530 531
            // this.addMobileCell(obj)
            // this.mobileTempCell.show = false
532 533
            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
534

535 536
            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
537
          }
538
        }
539
      },
540
      async cellCopy (event, annotation) {
541 542
        this.$store.commit('mosys/setEditingCells', '')

543
        this.$q.notify({
544
          message: 'Copied.',
545
          color: 'dark',
546 547
          textColor: 'white',
          position: 'bottom-right'
548 549 550 551 552 553 554 555 556 557
        })

        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)
      },
558
      addMobileCell (event) {
christianrhansen's avatar
christianrhansen committed
559
        if (this.cachedNewCell) {
560
          this.handleGridDrop(event)
christianrhansen's avatar
christianrhansen committed
561
          this.$store.commit('mosys/cacheNewCell', undefined)
562 563
          this.$q.notify({
            message: 'Cell was added.',
christianrhansen's avatar
christianrhansen committed
564 565 566
            color: 'primary',
            position: 'bottom-left',
            timeout: 800
567
          })
christianrhansen's avatar
christianrhansen committed
568 569
        }
      },
570
      touchMobileCell (event, cell) {
571
        if (!this.mobileTempCell.onGrid && cell) {
572 573 574 575 576 577 578 579 580
          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)
        }
581
      },
christianrhansen's avatar
christianrhansen committed
582
      handleCellTouch (event, annotation) {
583
        console.log('handleCellTouch', annotation)
584
        if (!this.cachedNewCell) {
585 586 587 588 589 590 591 592 593 594 595 596 597 598
          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

599
          this.selectedCell.annotation = annotation
600
          this.getCellType(annotation)
601
        }
christianrhansen's avatar
christianrhansen committed
602
      },
603 604 605 606 607
      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)
      },
608 609
      async mobileCellMove (annotation, x, y) {
        // this.$el.scrollLeft = this.$el.scrollLeft + (this.gridDimensions.full.cell.width * _x)
610

christianrhansen's avatar
christianrhansen committed
611
        let parsed = annotation.target.selector.parse()
612
        let target = this.grid.get2DArea([x, y], parsed.xywh.slice(2))
christianrhansen's avatar
christianrhansen committed
613

614 615 616 617 618 619 620
        annotation.target.selector.value = target.selector.value
        await this.$store.dispatch('annotations/patch', [annotation.id, {
          target: {
            selector: { value: target.selector.value }
          }
        }])
      },
621
      async mobileCellResize (annotation, _w, _h) {
622 623
        let
          parsed = annotation.target.selector.parse(),
christianrhansen's avatar
christianrhansen committed
624 625 626
          sliced = parsed.xywh.slice(0, 4),
          x = sliced[0],
          y = sliced[1]
627

628
        const value = { xywh: [x, y, _w, _h] }
629 630 631 632 633 634 635
        annotation.target.selector.value = value

        await this.$store.dispatch('annotations/patch', [annotation.id, { target: { selector: { value } } }])
      },
      setEditMode (mode) {
        this.$store.commit('mosys/setEditMode', mode)
      },
636 637 638 639
      //
      // DATA
      //

Anton's avatar
Intial  
Anton committed
640 641 642
      async fetchData () {
        if (this.gridUuid) {
          this.grid = await this.$store.dispatch('maps/get', this.gridUuid)
643 644 645 646 647 648 649 650
          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
651
          this.updateGridDimensions()
652 653 654 655 656 657
          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
658 659
        }
      },
660 661 662
      async updateGridMetadataStore () {
        await this.$store.dispatch('maps/patch', [this.grid.id, { config: this.grid.config }])
        this.updateGridDimensions()
Anton's avatar
Intial  
Anton committed
663
      },
664 665 666 667 668
      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
669 670
          // return _this.cells.find(c => c._uuid === k)
          return _this.annotations.find(c => c._uuid === k)
671 672
        })
        this.$store.commit('mosys/setSelectedCells', selectedCells)
Anton's avatar
Intial  
Anton committed
673
      },
Mathias Bär's avatar
Mathias Bär committed
674 675 676 677 678 679 680 681
      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)
        })
682 683 684 685 686 687 688
        /*
        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
689 690
        this.$store.commit('mosys/setEditingCells', editingCells)
      },
691 692 693 694 695
      updateAnnotationUIStates () {
        let newAnnotationUIStates = {}
        this.annotations.forEach(a => {
          newAnnotationUIStates[a._uuid] = {
            selected: false,
Mathias Bär's avatar
Mathias Bär committed
696
            editing: false,
697
            beingResized: false,
Mathias Bär's avatar
Mathias Bär committed
698
            beginDragged: false,
699
            annotation: a
Anton's avatar
Intial  
Anton committed
700
          }
701 702
        })
        this.annotationUIStates = newAnnotationUIStates
Anton's avatar
Intial  
Anton committed
703
        this.updateSelectedCells()
Mathias Bär's avatar
Mathias Bär committed
704
        this.updateEditingCells()
705
      },
706 707 708 709 710

      //
      // GRID DRAG & DROP HANDLERS
      //

711
      async handleGridDragOver (event) {
Anton's avatar
Intial  
Anton committed
712 713 714
        let _this = this
        if (this.resizingGrid) {
          const position = this.getGridPositionForEvent(event)
715 716
          this.grid.config.ratio = position.ox / (position.oy * 1.0)
          await this.updateGridMetadataStore()
Anton's avatar
Intial  
Anton committed
717 718
        }
        else {
719 720
          let annotation = this.annotations.filter(annotation => {
            if (!_this.annotationUIStates[annotation._uuid]) return false
Mathias Bär's avatar
Mathias Bär committed
721
            return _this.annotationUIStates[annotation._uuid].beingDragged ||
722
              _this.annotationUIStates[annotation._uuid].beingResized
Anton's avatar
Intial  
Anton committed
723 724
          }).shift()
          let offset, position
725 726 727 728
          if (!annotation) {
            annotation = {
              target: this.grid.get2DArea([1, 1], [1, 1])
            }
Anton's avatar
Intial  
Anton committed
729 730 731
            position = this.getGridPositionForEvent(event)
          }
          else {
732
            offset = this.annotationUIStates[annotation._uuid].draggingOffset
Anton's avatar
Intial  
Anton committed
733 734
            position = this.getGridPositionForEvent(event, offset)
          }
735 736
          if (!this.tmpObjects.length) this.tmpObjects.push(annotation)
          const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
737
          if (event.dataTransfer.types.includes('text/plain')) {
738 739
            parsed.xywh[0] = position.x
            parsed.xywh[1] = position.y
Anton's avatar
Intial  
Anton committed
740 741 742
            event.preventDefault()
          }
          else {
743 744
            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
745
          }
746
          annotation.target.selector.value = parsed
Anton's avatar
Intial  
Anton committed
747 748 749
        }
      },
      handleGridDragEnd () {
750
        this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
751
      },
752
      async handleGridDrop (event) {
753 754 755 756 757
        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)
758 759
        if (dropData) {
          dropData = JSON.parse(dropData)
760
          let annotation = this.annotations.find(a => a.id === dropData.id)
761 762 763 764 765
          const { x, y } = this.getGridPositionForEvent(
            event,
            annotation ? this.annotationUIStates[annotation._uuid].draggingOffset : undefined
          )
          if (annotation) {
766 767 768
            const
              parsed = annotation.target.selector.parse(),
              target = this.grid.get2DArea([x, y], parsed.xywh.slice(2))
769 770 771 772 773 774
            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
775 776
          }
          else {
777 778
            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])
779 780 781 782 783 784 785 786 787 788 789
            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
                }
              },
790 791
              // target: this.grid.get2DArea([x, y], [1, 1])
              target: test
792
            })
793
            this.annotations.push(annotation)
Anton's avatar
Anton committed
794
            this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
795
          }
796

797
          this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
798 799
          event.preventDefault()
        }
800
        this.clearHandler('temp cell')
Anton's avatar
Intial  
Anton committed
801
      },
802 803 804 805

      //
      // CELL DRAG & DROP HANDLERS
      //
Mathias Bär's avatar
Mathias Bär committed
806 807 808
      handleCellEditClick (event, cell) {
        this.annotationUIStates[cell._uuid].editing = !this.annotationUIStates[cell._uuid].editing
        this.updateEditingCells()
809
        this.$root.$emit('mosys_saveScrollPosition')
Mathias Bär's avatar
Mathias Bär committed
810
      },
811
      /*
812 813 814 815
      handleCellClick (event, cell) {
        this.annotationUIStates[cell._uuid].selected = !this.annotationUIStates[cell._uuid].selected
        this.updateSelectedCells()
      },
816
      */
817
      handleCellDragStart (event, annotation) {
christianrhansen's avatar
christianrhansen committed
818
        console.log('§§§§§§§§', annotation)
819 820 821 822 823 824 825 826 827 828 829 830
        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
831
          }
832
          this.tmpObjects.push(annotation)
833 834 835
        }
      },
      handleCellDragEnd (event, annotation) {
836 837 838
        if (!this.mobile) {
          this.annotationUIStates[annotation._uuid].beingDragged = false
        }
839 840 841 842
      },
      async handleCellContextMenuDelete (event, annotation) {
        this.annotationUIStates[annotation._uuid].selected = false
        this.updateSelectedCells()
Mathias Bär's avatar
Mathias Bär committed
843
        this.updateEditingCells()
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
        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
887 888
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
      },
889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
      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
906
        }
907 908 909 910 911 912 913 914 915
        catch (e) { /* dialog canceled */ }
      },

      //
      // GRID CONTEXT MENU
      //

      handleGridContextMenu (event) {
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
Anton's avatar
Intial  
Anton committed
916 917 918
      },
      async handleGridContextMenuInsertColumnLeft () {
        let position = this.contextMenuClickPosition
919 920 921 922 923 924 925 926
        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'