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>