GridEditor.vue 23.8 KB
Newer Older
Anton's avatar
Intial  
Anton committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<template lang="pug">

  div.cell-grid-container

    div.cell-grid(
      @dragenter="handleGridDragOver",
      @dragover="handleGridDragOver",
      @dragleave="handleGridDragEnd",
      @drop="handleGridDrop",
      @contextmenu="handleGridContextMenu",
      :style="gridStyle")

      q-context-menu(ref="gridmenu")
        q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
          q-item(
            v-for="action in gridContextMenuActions",
            :key="action.label",
            v-close-overlay,
            @click.native="event => {action.handler(event)}")
              q-item-main(:label="action.label")

      template(v-if="!resizingGrid")

24
        template(v-for="(annotation, index) in annotations")
Anton's avatar
Intial  
Anton committed
25
          .cell-item(
26
            v-if="!annotationUIStates[annotation._uuid] || !annotationUIStates[annotation._uuid].beingDragged",
Anton's avatar
Intial  
Anton committed
27
            draggable="true",
28 29
            @dragstart="event => {handleCellDragStart(event, annotation)}",
            @dragend="event => {handleCellDragEnd(event, annotation)}",
Anton's avatar
Intial  
Anton committed
30
            @contextmenu="handleCellContextMenu",
31 32 33
            :style="getAnnotationStyle(annotation)",
            @click.prevent="event => {handleCellClick(event, annotation)}",
            :class="{selected: annotationUIStates[annotation._uuid] ? annotationUIStates[annotation._uuid].selected : false}",
Anton's avatar
Intial  
Anton committed
34
            :key="`cell-${index}`")
35
              cell(:annotation="annotation", preview)
Anton's avatar
Intial  
Anton committed
36 37
              div.cell-item-resize-handle(
                draggable="true",
38 39 40
                @dragstart="event => {handleCellResizerDragStart(event, annotation)}",
                @dragend="event => {handleCellResizerDragEnd(event, annotation)}",
                @dragexit="event => {handleCellResizerDragEnd(event, annotation)}")
Anton's avatar
Intial  
Anton committed
41 42 43 44 45 46 47 48
                  q-icon(name="network cell")

              q-context-menu
                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,
49
                    @click.native="event => {action.handler(event, annotation)}")
Anton's avatar
Intial  
Anton committed
50 51
                      q-item-main(:label="action.label")

52
        template(v-for="(tmpCell, index) in tmpObjects")
53
          .cell-item.cell-item-tmp(:style="getAnnotationStyle(tmpCell)", :key="`cell-tmp-${index}`")
Anton's avatar
Intial  
Anton committed
54 55 56
            cell(:cell="tmpCell")

      template(v-else)
57
        .cell-item(:style="getAnnotationStyle({x:0,y:0,width:1,height:1})", key="cell-grid-resizer")
Anton's avatar
Intial  
Anton committed
58 59 60 61 62 63 64
          div.cell-item-resize-handle(
            draggable="true",
            @dragstart="event => {handleGridResizerDragStart(event)}",
            @dragend="event => {handleGridResizerDragEnd(event)}",
            @dragexit="event => {handleGridResizerDragEnd(event)}")
              q-icon(name="network cell")

65
      div.fixed-top-right(style="right:18px; top:68px", v-if="!$store.state.mosys.showSources")
Anton's avatar
Intial  
Anton committed
66 67
        q-btn(round, color="primary", small, @click="handleGridButtonClickEdit", style="margin-right: 0.5em")
          q-icon(name="add")
68
        q-btn(round, color="primary", small, @click="$router.push(`/mosys/grids/${$route.params.uuid}`)")
Anton's avatar
Intial  
Anton committed
69 70 71 72 73 74 75
          q-icon(name="remove red eye")

</template>

<script>
  import { ObjectUtil } from 'mbjs-utils'
  import Cell from './Cell'
76 77
  import { userHasFeature } from 'mbjs-quasar/src/lib'
  import { mapGetters } from 'vuex'
Anton's avatar
Intial  
Anton committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

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

  export default {
    components: {
      Cell
    },
    props: ['gridUuid'],
    data () {
      return {
        gridContextMenuActions: {
          // add_cell: {
          //   label: 'Add Cell',
          //   handler: this.handleGridContextMenuAddCell
          // },
          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,
116
        annotations: undefined,
Anton's avatar
Intial  
Anton committed
117 118
        gridMetadata: {},
        cells: [],
119
        tmpObjects: [],
120
        annotationUIStates: {},
Anton's avatar
Anton committed
121
        gridDimensions: { gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0 },
Anton's avatar
Intial  
Anton committed
122 123 124 125 126
        gridStyle: {},
        contextMenuClickPosition: {},
        resizingGrid: false
      }
    },
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
    computed: {
      ...mapGetters({
        user: 'auth/getUserState'
      }),
      cellContextMenuActions () {
        const actions = {
          // edit: {
          //   label: 'Edit',
          //   handler: this.handleCellContextMenuEdit
          // },
          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
      }
    },
Anton's avatar
Intial  
Anton committed
159 160 161 162 163 164 165 166 167
    async mounted () {
      window.addEventListener('resize', this.updateGridDimensions)
      await this.fetchData()
    },
    beforeDestroy () {
      window.removeEventListener('resize', this.updateGridDimensions)
    },
    watch: {
      cells () {
168
        this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
169 170 171 172 173 174 175 176 177 178 179 180
      },
      gridMetadata () {
        this.updateGridDimensions()
      },
      async gridUuid () {
        await this.fetchData()
      }
    },
    methods: {
      async fetchData () {
        if (this.gridUuid) {
          this.grid = await this.$store.dispatch('maps/get', this.gridUuid)
181 182 183 184 185 186 187 188 189
          if (!Object.keys(this.grid.config).length) {
            this.grid.config = {
              columns: 10,
              rows: 6,
              ratio: 16 / 9.0
            }
            console.log('conf', this.grid.config)
            await this.updateGridMetadataStore()
          }
Anton's avatar
Intial  
Anton committed
190
          this.updateGridDimensions()
191 192 193 194 195 196
          const { items } = await this.$store.dispatch('annotations/find', {
            'target.id': this.grid.id,
            'body.purpose': 'linking',
            'body.type': 'Cell'
          })
          this.annotations = items
197
          this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
198 199 200 201 202 203 204 205
        }
      },
      handleGridResizerDragStart (event) {
        event.dataTransfer.setDragImage(nullImage, 0, 0)
      },
      async handleGridResizerDragEnd () {
        await this.updateGridMetadataStore()
      },
206
      handleCellResizerDragStart (event, annotation) {
Anton's avatar
Intial  
Anton committed
207
        event.dataTransfer.setDragImage(nullImage, 0, 0)
208 209
        this.annotationUIStates[annotation._uuid].beingResized = true
        let tmpCell = this.getTmpCell(annotation)
210
        this.tmpObjects.push(tmpCell)
Anton's avatar
Intial  
Anton committed
211
      },
212
      async handleCellResizerDragEnd (event, annotation) {
Anton's avatar
Intial  
Anton committed
213
        let position = this.getGridPositionForEvent(event)
214 215 216 217 218 219 220 221
        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
222
        this.tmpObjects = []
223
        await this.$store.dispatch('annotations/patch', [annotation.id, { target: { selector: { value } } }])
Anton's avatar
Intial  
Anton committed
224 225
      },
      handleCellClick (event, cell) {
226
        this.annotationUIStates[cell._uuid].selected = !this.annotationUIStates[cell._uuid].selected
Anton's avatar
Intial  
Anton committed
227 228
        this.updateSelectedCells()
      },
229 230 231
      handleCellDragStart (event, annotation) {
        if (!this.annotationUIStates[annotation._uuid].beingResized) {
          event.dataTransfer.setData('text/plain', JSON.stringify(annotation))
Anton's avatar
Intial  
Anton committed
232 233
          event.dataTransfer.setDragImage(nullImage, 0, 0)
          let elContainerBoundingBox = this.$el.getBoundingClientRect()
234
          let elBoundingBox = event.target.getBoundingClientRect()
Anton's avatar
Intial  
Anton committed
235 236 237 238
          let offset = {
            x: (event.clientX - elContainerBoundingBox.x) - (elBoundingBox.x - elContainerBoundingBox.x),
            y: (event.clientY - elContainerBoundingBox.y) - (elBoundingBox.y - elContainerBoundingBox.y)
          }
239 240
          this.annotationUIStates[annotation._uuid].draggingOffset = offset
          this.annotationUIStates[annotation._uuid].beginDragged = true
Anton's avatar
Intial  
Anton committed
241
        }
242
        this.tmpObjects.push(annotation)
Anton's avatar
Intial  
Anton committed
243 244
      },
      handleCellDragEnd (event, cell) {
245
        this.annotationUIStates[cell._uuid].beingDragged = false
Anton's avatar
Intial  
Anton committed
246 247 248 249
      },
      // handleCellContextMenuClick () {
      // },
      // handleCellContextMenuEdit (/* event, cell */) {
250 251
      //   // this.$store.commit('mosys/showSources')
      //   // this.$store.commit('mosys/setSourcesTab', 'tab-default-cells')
Anton's avatar
Intial  
Anton committed
252
      // },
253 254
      async handleCellContextMenuDelete (event, annotation) {
        this.annotationUIStates[annotation._uuid].selected = false
Anton's avatar
Intial  
Anton committed
255
        this.updateSelectedCells()
256 257 258
        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)
Anton's avatar
Intial  
Anton committed
259 260 261 262
      },
      handleCellContextMenu (event) {
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
      },
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
      async handleCellContextMenuEditCSS (event, cell) {
        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: cell.styleClass,
              type: 'text'
            }
          })
          if (styleClass) {
            if (!styleClass.length) styleClass = undefined
            else if (styleClass.indexOf('.') === 0) styleClass = styleClass.substr(1)
          }
          cell.styleClass = styleClass
279
          this.$store.dispatch('annotations/patch', [cell._uuid, { target: { styleClass: styleClass || null } }])
280 281 282
        }
        catch (e) { /* dialog canceled */ }
      },
283
      async handleGridDragOver (event) {
Anton's avatar
Intial  
Anton committed
284 285 286
        let _this = this
        if (this.resizingGrid) {
          const position = this.getGridPositionForEvent(event)
287 288
          this.grid.config.ratio = position.ox / (position.oy * 1.0)
          await this.updateGridMetadataStore()
Anton's avatar
Intial  
Anton committed
289 290
        }
        else {
291 292 293 294
          let annotation = this.annotations.filter(annotation => {
            if (!_this.annotationUIStates[annotation._uuid]) return false
            return _this.annotationUIStates[annotation._uuid].beginDragged ||
              _this.annotationUIStates[annotation._uuid].beingResized
Anton's avatar
Intial  
Anton committed
295 296
          }).shift()
          let offset, position
297 298 299 300
          if (!annotation) {
            annotation = {
              target: this.grid.get2DArea([1, 1], [1, 1])
            }
Anton's avatar
Intial  
Anton committed
301 302 303
            position = this.getGridPositionForEvent(event)
          }
          else {
304
            offset = this.annotationUIStates[annotation._uuid].draggingOffset
Anton's avatar
Intial  
Anton committed
305 306
            position = this.getGridPositionForEvent(event, offset)
          }
307 308
          if (!this.tmpObjects.length) this.tmpObjects.push(annotation)
          const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
309
          if (event.dataTransfer.types.includes('text/plain')) {
310
            annotation.target.selector.value = { xywh: [position.x, position.y] }
311 312
            parsed.xywh[0] = position.x
            parsed.xywh[1] = position.y
Anton's avatar
Intial  
Anton committed
313 314 315
            event.preventDefault()
          }
          else {
316 317
            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
318
          }
319
          annotation.target.selector.value = parsed
Anton's avatar
Intial  
Anton committed
320 321 322
        }
      },
      handleGridDragEnd () {
323
        this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
324
      },
325 326 327 328
      async handleGridDrop (event) {
        let dropData = event.dataTransfer.getData('text/plain')
        if (dropData) {
          dropData = JSON.parse(dropData)
329
          let annotation = this.annotations.find(a => a.id === dropData.id)
330 331 332 333 334 335 336 337 338 339 340 341 342
          const { x, y } = this.getGridPositionForEvent(
            event,
            annotation ? this.annotationUIStates[annotation._uuid].draggingOffset : undefined
          )
          const target = this.grid.get2DArea([x, y], [1, 1])
          if (annotation) {
            annotation.target.selector.value = target.selector.value
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: {
                selector: { value: target.selector.value }
              }
            }])
            console.debug('dropped existing annotation', annotation)
Anton's avatar
Intial  
Anton committed
343 344
          }
          else {
345 346 347 348 349 350 351 352 353 354 355 356 357 358
            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
                }
              },
              target
            })
            console.debug('dropped new cell', cell, annotation)
Anton's avatar
Intial  
Anton committed
359
          }
360

361
          this.tmpObjects = []
362
          // this.updateCellStore(cell)
Anton's avatar
Intial  
Anton committed
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
          event.preventDefault()
        }
      },
      handleGridContextMenu (event) {
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
      },
      async handleGridContextMenuAddCell () {
        let position = this.contextMenuClickPosition
        let newCell = {
          x: position.x,
          y: position.y,
          width: 1,
          height: 1,
          type: 'text',
          content: 'A new cell is born'
        }
        let annotation = this.getGridCellAnnotation(newCell)
        await this.$store.dispatch('annotations/post', annotation)
        await this.fetchCellAnnotations()
      },
      async handleGridContextMenuInsertColumnLeft () {
        let position = this.contextMenuClickPosition
        for (let cell of this.cells) {
          if (cell.x >= position.x) {
            cell.x += 1
            let annotation = this.getGridCellAnnotation(cell)
389
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
390 391 392 393
            await this.fetchCellAnnotations()
          }
        }

Anton's avatar
Anton committed
394
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { columns: this.gridMetadata.columns + 1 })
Anton's avatar
Intial  
Anton committed
395 396 397 398 399 400 401 402
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteColumn () {
        let position = this.contextMenuClickPosition
        for (let cell of this.cells) {
          if (cell.x > position.x) {
            cell.x -= 1
            let annotation = this.getGridCellAnnotation(cell)
403
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
404 405 406 407
            await this.fetchCellAnnotations()
          }
        }

Anton's avatar
Anton committed
408
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { columns: this.gridMetadata.columns - 1 })
Anton's avatar
Intial  
Anton committed
409 410 411 412 413 414 415 416
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuInsertRowAbove () {
        let position = this.contextMenuClickPosition
        for (let cell of this.cells) {
          if (cell.y >= position.y) {
            cell.y += 1
            let annotation = this.getGridCellAnnotation(cell)
417
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
418 419 420
            await this.fetchCellAnnotations()
          }
        }
Anton's avatar
Anton committed
421
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { rows: this.gridMetadata.rows + 1 })
Anton's avatar
Intial  
Anton committed
422 423 424 425 426 427 428 429 430

        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteRow () {
        let position = this.contextMenuClickPosition
        for (let cell of this.cells) {
          if (cell.y > position.y) {
            cell.y -= 1
            let annotation = this.getGridCellAnnotation(cell)
431
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
432 433 434
            await this.fetchCellAnnotations()
          }
        }
Anton's avatar
Anton committed
435
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { rows: this.gridMetadata.rows - 1 })
Anton's avatar
Intial  
Anton committed
436 437 438 439

        await this.updateGridMetadataStore()
      },
      handleGridButtonClickEdit () {
440
        this.$store.commit('mosys/toggleSources')
Anton's avatar
Intial  
Anton committed
441 442 443
      },
      updateSelectedCells () {
        const _this = this
444 445
        let selectedCells = Object.keys(this.annotationUIStates).filter(k => {
          return _this.annotationUIStates[k].selected
Anton's avatar
Intial  
Anton committed
446
        }).map(k => {
447
          return _this.cells.find(c => c._uuid === k)
Anton's avatar
Intial  
Anton committed
448
        })
449
        this.$store.commit('mosys/setSelectedCells', selectedCells)
Anton's avatar
Intial  
Anton committed
450
      },
451 452
      updateAnnotationUIStates () {
        let newAnnotationUIStates = {}
453
        this.annotations.forEach(a => {
454
          newAnnotationUIStates[a._uuid] = {
Anton's avatar
Intial  
Anton committed
455 456
            selected: false,
            beingResized: false,
457
            annotation: a
Anton's avatar
Intial  
Anton committed
458 459
          }
        })
460
        this.annotationUIStates = newAnnotationUIStates
Anton's avatar
Intial  
Anton committed
461 462 463 464
        this.updateSelectedCells()
      },
      getTmpCell (cell, type = 'UIFeedback') {
        return {
465
          srcUuid: cell._uuid,
Anton's avatar
Intial  
Anton committed
466 467 468 469 470 471 472
          type: type,
          x: cell.x,
          y: cell.y,
          width: cell.width,
          height: cell.height
        }
      },
Anton's avatar
Anton committed
473 474
      getGridPositionForEvent (event, offset = { x: 0, y: 0 }) {
        offset = { x: 0, y: 0 } // TODO: remove quick fix
Anton's avatar
Intial  
Anton committed
475 476 477 478 479
        const elContainerBoundingBox = this.$el.getBoundingClientRect()
        const ox = event.clientX + this.$el.scrollLeft - elContainerBoundingBox.x - offset.x
        const oy = event.clientY + this.$el.scrollTop - elContainerBoundingBox.y - offset.y
        const x = Math.ceil(ox / this.gridDimensions.full.cell.width)
        const y = Math.ceil(oy / this.gridDimensions.full.cell.height)
Anton's avatar
Anton committed
480
        return { x: x, y: y, ox: ox, oy: oy }
Anton's avatar
Intial  
Anton committed
481 482 483 484
      },
      updateGridDimensions () {
        let elWidth = this.$el.offsetWidth
        let elHeight = this.$el.offsetHeight
485
        let cellSizeRatio = this.grid.config.ratio
Anton's avatar
Intial  
Anton committed
486
        let gridHeight = elHeight
487
        let cellHeight = gridHeight / this.grid.config.rows
Anton's avatar
Intial  
Anton committed
488
        let cellWidth = elWidth / Math.round(elWidth / (cellHeight * cellSizeRatio))
489
        let gridWidth = cellWidth * this.grid.config.columns
Anton's avatar
Intial  
Anton committed
490
        let cellsPerWidth = elWidth / cellWidth
491
        let cellWidthMini = elWidth / this.grid.config.columns
Anton's avatar
Intial  
Anton committed
492 493 494 495 496 497 498 499 500 501 502 503 504
        let gridHeightMini = cellWidthMini / cellSizeRatio
        this.gridDimensions = {
          full: {
            width: gridWidth,
            height: gridHeight,
            cell: {
              width: cellWidth,
              height: cellHeight
            },
            cells_per_width: cellsPerWidth
          },
          mini: {
            width: elWidth,
505
            height: gridHeightMini * this.grid.config.rows,
Anton's avatar
Intial  
Anton committed
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
            cell: {
              width: cellWidthMini,
              height: gridHeightMini
            }
          }
        }
        // TODO: fix mobile grid editor view
        // if (elWidth > 800) {
        this.gridStyle = {
          width: this.gridDimensions.full.width + 'px',
          height: '100%',
          'grid-auto-columns': this.gridDimensions.full.cell.width + 'px',
          'grid-auto-rows': this.gridDimensions.full.cell.height + 'px',
          'background-image': this.getGridBackgroundSVG(this.gridDimensions.full.cell)
        }
        /* }
        else {
          this.gridStyle = {
            width: '100%',
            height: this.gridDimensions.mini.height + 'px',
            'grid-auto-columns': this.gridDimensions.mini.cell.width + 'px',
            'grid-auto-rows': this.gridDimensions.mini.cell.height + 'px',
            'background-image': this.getGridBackgroundSVG(this.gridDimensions.mini.cell)
          }
        } */
      },
      getGridBackgroundSVG (cell) {
        return `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>")`
      },
      async fetchCellAnnotations () {
        const query = {
          type: 'Annotation',
          'body.type': '2DCell',
          'target.id': this.grid.id
        }
        const result = await this.$store.dispatch('annotations/find', query)
        this.cells = result.items.map(annotation => {
          let cell = JSON.parse(annotation.body.value)
          if (cell) {
549
            cell._uuid = annotation._uuid
550 551
            cell.id = annotation.id
            cell.styleClass = annotation.target ? annotation.target.styleClass : undefined
552
            cell.author = annotation.author
Anton's avatar
Intial  
Anton committed
553 554 555 556
            return cell
          }
          return null
        }).filter(cell => cell)
557
        this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
558 559 560 561 562 563 564 565 566 567 568 569
      },
      getGridCellAnnotation (cell) {
        return {
          body: {
            type: '2DCell',
            purpose: 'linking',
            value: JSON.stringify(cell)
          },
          target: {
            id: this.grid.id,
            type: 'Map',
            selector: {
570 571 572 573 574
              conformsTo: 'Media',
              type: 'FragmentSelector',
              value: {
                // xywh: [x, y, width, height]
              }
Anton's avatar
Intial  
Anton committed
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
            }
          }
        }
      },
      getGridMetadataAnnotation (id, metadata) {
        return {
          body: {
            type: '2DGridMetadata',
            purpose: 'linking',
            value: JSON.stringify(metadata)
          },
          target: {
            id,
            type: 'Map'
          }
        }
      },
      async updateCellStore (cell) {
        let annotation = this.getGridCellAnnotation(cell)
594 595
        if (cell._uuid) {
          await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
596 597 598 599 600 601 602
        }
        else {
          await this.$store.dispatch('annotations/post', annotation)
        }
        await this.fetchCellAnnotations()
      },
      async updateGridMetadataStore () {
603
        await this.$store.dispatch('maps/patch', [this.grid.id, { config: this.grid.config }])
Anton's avatar
Intial  
Anton committed
604 605
        this.updateGridDimensions()
      },
606 607
      getAnnotationStyle (annotation) {
        const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
608
        return {
609 610 611 612
          'grid-column-start': parsed.xywh[0],
          'grid-column-end': `span ${parsed.xywh[2]}`,
          'grid-row-start': parsed.xywh[1],
          'grid-row-end': `span ${parsed.xywh[3]}`
Anton's avatar
Intial  
Anton committed
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
        }
      }
      // setCellSet: function (cellSet) {
      //   this.grid = cellSet
      // }
    }
  }
</script>

<style scoped lang="stylus">

  .cell-grid
    display grid
    background-color #eee

  .cell-item

    &
      background-color rgba(0,0,0,0.2)
      border 1px solid #777
      margin 1px
      box-sizing border-box
      position relative
      overflow: hidden
      grid-column-start: 1
      grid-column-end: span 1
      grid-row-start: 1
      grid-row-end: span 1

    &:hover
      background-color lightblue

    &.selected
      background-color lightpink

    .cell-item-inner
      width 100%
      height 100%

    .cell-item-resize-handle
      color rgba(0,0,0,0.2)
      position absolute
      right 0
      bottom 0
      width 18px
      height 20px

      &:hover
        color black

</style>