GridEditor.vue 23.7 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
52
                      q-item-main(:label="action.label")

        template(v-for="(tmpCell, index) in tmpCells")
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
119
        gridMetadata: {},
        cells: [],
        tmpCells: [],
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
Anton's avatar
Intial  
Anton committed
197
198
199
200
201
202
203
204
        }
      },
      handleGridResizerDragStart (event) {
        event.dataTransfer.setDragImage(nullImage, 0, 0)
      },
      async handleGridResizerDragEnd () {
        await this.updateGridMetadataStore()
      },
205
      handleCellResizerDragStart (event, annotation) {
Anton's avatar
Intial  
Anton committed
206
        event.dataTransfer.setDragImage(nullImage, 0, 0)
207
208
        this.annotationUIStates[annotation._uuid].beingResized = true
        let tmpCell = this.getTmpCell(annotation)
Anton's avatar
Intial  
Anton committed
209
210
        this.tmpCells.push(tmpCell)
      },
211
      async handleCellResizerDragEnd (event, annotation) {
Anton's avatar
Intial  
Anton committed
212
        let position = this.getGridPositionForEvent(event)
213
214
215
216
217
218
219
220
        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
Anton's avatar
Intial  
Anton committed
221
        this.tmpCells = []
222
        await this.$store.dispatch('annotations/patch', [annotation.id, { target: { selector: { value } } }])
Anton's avatar
Intial  
Anton committed
223
224
      },
      handleCellClick (event, cell) {
225
        this.annotationUIStates[cell._uuid].selected = !this.annotationUIStates[cell._uuid].selected
Anton's avatar
Intial  
Anton committed
226
227
228
        this.updateSelectedCells()
      },
      handleCellDragStart (event, cell) {
229
        if (!this.annotationUIStates[cell._uuid].beingResized) {
Anton's avatar
Intial  
Anton committed
230
231
232
          event.dataTransfer.setData('text/plain', JSON.stringify(cell))
          event.dataTransfer.setDragImage(nullImage, 0, 0)
          let elContainerBoundingBox = this.$el.getBoundingClientRect()
233
          let elBoundingBox = event.target.getBoundingClientRect()
Anton's avatar
Intial  
Anton committed
234
235
236
237
          let offset = {
            x: (event.clientX - elContainerBoundingBox.x) - (elBoundingBox.x - elContainerBoundingBox.x),
            y: (event.clientY - elContainerBoundingBox.y) - (elBoundingBox.y - elContainerBoundingBox.y)
          }
238
239
          this.annotationUIStates[cell._uuid].draggingOffset = offset
          this.annotationUIStates[cell._uuid].beginDragged = true
Anton's avatar
Intial  
Anton committed
240
241
242
243
244
        }
        let tmpCell = this.getTmpCell(cell)
        this.tmpCells.push(tmpCell)
      },
      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
      // },
      async handleCellContextMenuDelete (event, cell) {
254
        this.annotationUIStates[cell._uuid].selected = false
Anton's avatar
Intial  
Anton committed
255
256
        this.updateSelectedCells()
        this.cells = this.cells.filter(c => c !== cell)
257
        await this.$store.dispatch('annotations/delete', cell._uuid)
Anton's avatar
Intial  
Anton committed
258
259
260
261
262
        this.fetchCellAnnotations()
      },
      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
          if (!this.tmpCells.length) this.tmpCells.push(annotation)
Anton's avatar
Intial  
Anton committed
308
          let tmpCell = this.tmpCells[0]
309
          const parsed = tmpCell.target.selector.parse()
Anton's avatar
Intial  
Anton committed
310
          if (event.dataTransfer.types.includes('text/plain')) {
311
312
313
            tmpCell.target.selector.value = { xywh: [position.x, position.y] }
            parsed.xywh[0] = position.x
            parsed.xywh[1] = position.y
Anton's avatar
Intial  
Anton committed
314
315
316
            event.preventDefault()
          }
          else {
317
318
            parsed.xywh[2] = Math.max(1, 1 + position.x - tmpCell.x)
            parsed.xywh[3] = Math.max(1, 1 + position.y - tmpCell.y)
Anton's avatar
Intial  
Anton committed
319
          }
320
          tmpCell.target.selector.value = parsed
Anton's avatar
Intial  
Anton committed
321
322
323
324
325
        }
      },
      handleGridDragEnd () {
        this.tmpCells = []
      },
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
      async handleGridDrop (event) {
        let dropData = event.dataTransfer.getData('text/plain')
        if (dropData) {
          dropData = JSON.parse(dropData)
          let annotation = this.cells.find(c => c.id === dropData.id)
          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
344
345
          }
          else {
346
347
348
349
350
351
352
353
354
355
356
357
358
359
            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
360
          }
361

Anton's avatar
Intial  
Anton committed
362
          this.tmpCells = []
363
          // this.updateCellStore(cell)
Anton's avatar
Intial  
Anton committed
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
389
          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)
390
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
391
392
393
394
            await this.fetchCellAnnotations()
          }
        }

Anton's avatar
Anton committed
395
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { columns: this.gridMetadata.columns + 1 })
Anton's avatar
Intial  
Anton committed
396
397
398
399
400
401
402
403
        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)
404
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
405
406
407
408
            await this.fetchCellAnnotations()
          }
        }

Anton's avatar
Anton committed
409
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { columns: this.gridMetadata.columns - 1 })
Anton's avatar
Intial  
Anton committed
410
411
412
413
414
415
416
417
        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)
418
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
419
420
421
            await this.fetchCellAnnotations()
          }
        }
Anton's avatar
Anton committed
422
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { rows: this.gridMetadata.rows + 1 })
Anton's avatar
Intial  
Anton committed
423
424
425
426
427
428
429
430
431

        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)
432
            await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
433
434
435
            await this.fetchCellAnnotations()
          }
        }
Anton's avatar
Anton committed
436
        this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { rows: this.gridMetadata.rows - 1 })
Anton's avatar
Intial  
Anton committed
437
438
439
440

        await this.updateGridMetadataStore()
      },
      handleGridButtonClickEdit () {
441
        this.$store.commit('mosys/toggleSources')
Anton's avatar
Intial  
Anton committed
442
443
444
      },
      updateSelectedCells () {
        const _this = this
445
446
        let selectedCells = Object.keys(this.annotationUIStates).filter(k => {
          return _this.annotationUIStates[k].selected
Anton's avatar
Intial  
Anton committed
447
        }).map(k => {
448
          return _this.cells.find(c => c._uuid === k)
Anton's avatar
Intial  
Anton committed
449
        })
450
        this.$store.commit('mosys/setSelectedCells', selectedCells)
Anton's avatar
Intial  
Anton committed
451
      },
452
453
454
455
      updateAnnotationUIStates () {
        let newAnnotationUIStates = {}
        this.annotations.map(a => {
          newAnnotationUIStates[a._uuid] = {
Anton's avatar
Intial  
Anton committed
456
457
            selected: false,
            beingResized: false,
458
            annotation: a
Anton's avatar
Intial  
Anton committed
459
460
          }
        })
461
        this.annotationUIStates = newAnnotationUIStates
Anton's avatar
Intial  
Anton committed
462
463
464
465
        this.updateSelectedCells()
      },
      getTmpCell (cell, type = 'UIFeedback') {
        return {
466
          srcUuid: cell._uuid,
Anton's avatar
Intial  
Anton committed
467
468
469
470
471
472
473
          type: type,
          x: cell.x,
          y: cell.y,
          width: cell.width,
          height: cell.height
        }
      },
Anton's avatar
Anton committed
474
475
      getGridPositionForEvent (event, offset = { x: 0, y: 0 }) {
        offset = { x: 0, y: 0 } // TODO: remove quick fix
Anton's avatar
Intial  
Anton committed
476
477
478
479
480
        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
481
        return { x: x, y: y, ox: ox, oy: oy }
Anton's avatar
Intial  
Anton committed
482
483
484
485
      },
      updateGridDimensions () {
        let elWidth = this.$el.offsetWidth
        let elHeight = this.$el.offsetHeight
486
        let cellSizeRatio = this.grid.config.ratio
Anton's avatar
Intial  
Anton committed
487
        let gridHeight = elHeight
488
        let cellHeight = gridHeight / this.grid.config.rows
Anton's avatar
Intial  
Anton committed
489
        let cellWidth = elWidth / Math.round(elWidth / (cellHeight * cellSizeRatio))
490
        let gridWidth = cellWidth * this.grid.config.columns
Anton's avatar
Intial  
Anton committed
491
        let cellsPerWidth = elWidth / cellWidth
492
        let cellWidthMini = elWidth / this.grid.config.columns
Anton's avatar
Intial  
Anton committed
493
494
495
496
497
498
499
500
501
502
503
504
505
        let gridHeightMini = cellWidthMini / cellSizeRatio
        this.gridDimensions = {
          full: {
            width: gridWidth,
            height: gridHeight,
            cell: {
              width: cellWidth,
              height: cellHeight
            },
            cells_per_width: cellsPerWidth
          },
          mini: {
            width: elWidth,
506
            height: gridHeightMini * this.grid.config.rows,
Anton's avatar
Intial  
Anton committed
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
549
            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) {
550
            cell._uuid = annotation._uuid
551
552
            cell.id = annotation.id
            cell.styleClass = annotation.target ? annotation.target.styleClass : undefined
553
            cell.author = annotation.author
Anton's avatar
Intial  
Anton committed
554
555
556
557
            return cell
          }
          return null
        }).filter(cell => cell)
558
        this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
559
560
561
562
563
564
565
566
567
568
569
570
      },
      getGridCellAnnotation (cell) {
        return {
          body: {
            type: '2DCell',
            purpose: 'linking',
            value: JSON.stringify(cell)
          },
          target: {
            id: this.grid.id,
            type: 'Map',
            selector: {
571
572
573
574
575
              conformsTo: 'Media',
              type: 'FragmentSelector',
              value: {
                // xywh: [x, y, width, height]
              }
Anton's avatar
Intial  
Anton committed
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
            }
          }
        }
      },
      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)
595
596
        if (cell._uuid) {
          await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
Anton's avatar
Intial  
Anton committed
597
598
599
600
601
602
603
        }
        else {
          await this.$store.dispatch('annotations/post', annotation)
        }
        await this.fetchCellAnnotations()
      },
      async updateGridMetadataStore () {
604
        await this.$store.dispatch('maps/patch', [this.grid.id, { config: this.grid.config }])
Anton's avatar
Intial  
Anton committed
605
606
        this.updateGridDimensions()
      },
607
608
      getAnnotationStyle (annotation) {
        const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
609
        return {
610
611
612
613
          '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
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
664
        }
      }
      // 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>