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

  div.cell-grid-container
4
    q-window-resize-observable(@resize="updateGridDimensions")
Anton's avatar
Intial  
Anton committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

    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")

25
        template(v-for="(annotation, index) in annotations")
Anton's avatar
Intial  
Anton committed
26
          .cell-item(
27
            v-if="!annotationUIStates[annotation._uuid] || !annotationUIStates[annotation._uuid].beingDragged",
Anton's avatar
Intial  
Anton committed
28
            draggable="true",
29
30
            @dragstart="event => {handleCellDragStart(event, annotation)}",
            @dragend="event => {handleCellDragEnd(event, annotation)}",
Anton's avatar
Intial  
Anton committed
31
            @contextmenu="handleCellContextMenu",
32
            :style="getAnnotationStyle(annotation)",
Mathias Bär's avatar
Mathias Bär committed
33
            :class="getAnnotationClasses(annotation._uuid, 'cell-item')",
Anton's avatar
Intial  
Anton committed
34
            :key="`cell-${index}`")
Mathias Bär's avatar
Mathias Bär committed
35
36
37
38
39
40
              q-btn.edit-button.absolute-top-right(
                @click.prevent="event => {handleCellEditClick(event, annotation)}",
                :class="getAnnotationClasses(annotation._uuid, 'editing')",
                style="top: 8px; right: 8px;",
                :icon="annotationUIStates[annotation._uuid].editing ? 'close' : 'edit'", flat, round, size="md"
                )
41
42
43
44
              // selecting cells disabled because it has no use currently
              // switch with cell component below to re-enable it
              //cell(
                @click.native.prevent="event => {handleCellClick(event, annotation)}", :annotation="annotation", :preview="true")
Mathias Bär's avatar
Mathias Bär committed
45
46
47
48
              cell(
                :annotation="annotation",
                :preview="true"
                )
Anton's avatar
Intial  
Anton committed
49
50
              div.cell-item-resize-handle(
                draggable="true",
51
52
53
                @dragstart="event => {handleCellResizerDragStart(event, annotation)}",
                @dragend="event => {handleCellResizerDragEnd(event, annotation)}",
                @dragexit="event => {handleCellResizerDragEnd(event, annotation)}")
Anton's avatar
Intial  
Anton committed
54
55
56
57
58
59
60
61
                  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,
62
                    @click.native="event => {action.handler(event, annotation)}")
Anton's avatar
Intial  
Anton committed
63
64
                      q-item-main(:label="action.label")

65
        template(v-for="(tmpCell, index) in tmpObjects")
66
          .cell-item.cell-item-tmp(:style="getAnnotationStyle(tmpCell)", :key="`cell-tmp-${index}`")
Anton's avatar
Intial  
Anton committed
67
68
69
            cell(:cell="tmpCell")

      template(v-else)
70
        .cell-item(:style="getAnnotationStyle({x:0,y:0,width:1,height:1})", key="cell-grid-resizer")
Anton's avatar
Intial  
Anton committed
71
72
73
74
75
76
77
          div.cell-item-resize-handle(
            draggable="true",
            @dragstart="event => {handleGridResizerDragStart(event)}",
            @dragend="event => {handleGridResizerDragEnd(event)}",
            @dragexit="event => {handleGridResizerDragEnd(event)}")
              q-icon(name="network cell")

Mathias Bär's avatar
Mathias Bär committed
78
      //template(v-if="!isMobile")
79
80
81
82
83
        .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
84
      //template(v-if="isMobile")
Mathias Bär's avatar
Mathias Bär committed
85
86
87
88
89
        .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
90
91
92
93
94

</template>

<script>
  import Cell from './Cell'
95
96
  import { userHasFeature } from 'mbjs-quasar/src/lib'
  import { mapGetters } from 'vuex'
Anton's avatar
Intial  
Anton committed
97
98
99
100
101
102
103
104

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

  export default {
    components: {
      Cell
    },
105
    props: ['gridUuid', 'tabsAreOpen'],
Anton's avatar
Intial  
Anton committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    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,
131
        annotations: undefined,
132
        tmpObjects: [],
133
        annotationUIStates: {},
Anton's avatar
Anton committed
134
        gridDimensions: { gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0 },
Anton's avatar
Intial  
Anton committed
135
136
137
138
        contextMenuClickPosition: {},
        resizingGrid: false
      }
    },
139
140
    computed: {
      ...mapGetters({
141
        user: 'auth/getUserState',
Mathias Bär's avatar
Mathias Bär committed
142
143
        isMobile: 'globalSettings/getIsMobile',
        // editingCells: 'mosys/getEditingCells'
144
145
        showEditingCells: 'mosys/getShowEditingCells',
        scrollPositionCache: 'mosys/getScrollPositionCache'
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
      }),
      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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
      },
      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>")`
        }
185
186
      }
    },
Anton's avatar
Intial  
Anton committed
187
188
    async mounted () {
      await this.fetchData()
189
      this.resetScrollPosition()
Anton's avatar
Intial  
Anton committed
190
191
    },
    watch: {
Anton's avatar
Anton committed
192
      annotations () {
193
        this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
194
195
196
197
198
199
      },
      gridMetadata () {
        this.updateGridDimensions()
      },
      async gridUuid () {
        await this.fetchData()
Mathias Bär's avatar
Mathias Bär committed
200
201
202
203
204
205
      },
      showEditingCells (val) {
        console.log('show editing cells', val)
        if (val === false) {
          this.updateAnnotationUIStates()
        }
206
207
208
      },
      tabsAreOpen () {
        this.resetScrollPosition()
Anton's avatar
Intial  
Anton committed
209
210
211
      }
    },
    methods: {
212
213
214
215
      //
      // DATA
      //

Anton's avatar
Intial  
Anton committed
216
217
218
      async fetchData () {
        if (this.gridUuid) {
          this.grid = await this.$store.dispatch('maps/get', this.gridUuid)
219
220
221
222
223
224
225
226
          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
227
          this.updateGridDimensions()
228
229
230
231
232
233
          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
234
235
        }
      },
236
237
238
      async updateGridMetadataStore () {
        await this.$store.dispatch('maps/patch', [this.grid.id, { config: this.grid.config }])
        this.updateGridDimensions()
Anton's avatar
Intial  
Anton committed
239
      },
240
241
242
243
244
      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
245
246
          // return _this.cells.find(c => c._uuid === k)
          return _this.annotations.find(c => c._uuid === k)
247
248
        })
        this.$store.commit('mosys/setSelectedCells', selectedCells)
Anton's avatar
Intial  
Anton committed
249
      },
Mathias Bär's avatar
Mathias Bär committed
250
251
252
253
254
255
256
257
258
259
      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)
        })
        this.$store.commit('mosys/setEditingCells', editingCells)
      },
260
261
262
263
264
      updateAnnotationUIStates () {
        let newAnnotationUIStates = {}
        this.annotations.forEach(a => {
          newAnnotationUIStates[a._uuid] = {
            selected: false,
Mathias Bär's avatar
Mathias Bär committed
265
            editing: false,
266
            beingResized: false,
Mathias Bär's avatar
Mathias Bär committed
267
            beginDragged: false,
268
            annotation: a
Anton's avatar
Intial  
Anton committed
269
          }
270
271
        })
        this.annotationUIStates = newAnnotationUIStates
Anton's avatar
Intial  
Anton committed
272
        this.updateSelectedCells()
Mathias Bär's avatar
Mathias Bär committed
273
        this.updateEditingCells()
274
      },
275
276
277
278
279

      //
      // GRID DRAG & DROP HANDLERS
      //

280
      async handleGridDragOver (event) {
Anton's avatar
Intial  
Anton committed
281
282
283
        let _this = this
        if (this.resizingGrid) {
          const position = this.getGridPositionForEvent(event)
284
285
          this.grid.config.ratio = position.ox / (position.oy * 1.0)
          await this.updateGridMetadataStore()
Anton's avatar
Intial  
Anton committed
286
287
        }
        else {
288
289
          let annotation = this.annotations.filter(annotation => {
            if (!_this.annotationUIStates[annotation._uuid]) return false
Mathias Bär's avatar
Mathias Bär committed
290
            return _this.annotationUIStates[annotation._uuid].beingDragged ||
291
              _this.annotationUIStates[annotation._uuid].beingResized
Anton's avatar
Intial  
Anton committed
292
293
          }).shift()
          let offset, position
294
295
296
297
          if (!annotation) {
            annotation = {
              target: this.grid.get2DArea([1, 1], [1, 1])
            }
Anton's avatar
Intial  
Anton committed
298
299
300
            position = this.getGridPositionForEvent(event)
          }
          else {
301
            offset = this.annotationUIStates[annotation._uuid].draggingOffset
Anton's avatar
Intial  
Anton committed
302
303
            position = this.getGridPositionForEvent(event, offset)
          }
304
305
          if (!this.tmpObjects.length) this.tmpObjects.push(annotation)
          const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
306
          if (event.dataTransfer.types.includes('text/plain')) {
307
308
            parsed.xywh[0] = position.x
            parsed.xywh[1] = position.y
Anton's avatar
Intial  
Anton committed
309
310
311
            event.preventDefault()
          }
          else {
312
313
            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
314
          }
315
          annotation.target.selector.value = parsed
Anton's avatar
Intial  
Anton committed
316
317
318
        }
      },
      handleGridDragEnd () {
319
        this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
320
      },
321
322
323
324
      async handleGridDrop (event) {
        let dropData = event.dataTransfer.getData('text/plain')
        if (dropData) {
          dropData = JSON.parse(dropData)
325
          let annotation = this.annotations.find(a => a.id === dropData.id)
326
327
328
329
330
          const { x, y } = this.getGridPositionForEvent(
            event,
            annotation ? this.annotationUIStates[annotation._uuid].draggingOffset : undefined
          )
          if (annotation) {
331
332
333
            const
              parsed = annotation.target.selector.parse(),
              target = this.grid.get2DArea([x, y], parsed.xywh.slice(2))
334
335
336
337
338
339
            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
340
341
          }
          else {
342
343
344
345
346
347
348
349
350
351
352
            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
                }
              },
353
              target: this.grid.get2DArea([x, y], [1, 1])
354
            })
355
            this.annotations.push(annotation)
Anton's avatar
Anton committed
356
            this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
357
          }
358

359
          this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
360
361
362
          event.preventDefault()
        }
      },
363
364
365
366

      //
      // CELL DRAG & DROP HANDLERS
      //
Mathias Bär's avatar
Mathias Bär committed
367
368
369
      handleCellEditClick (event, cell) {
        this.annotationUIStates[cell._uuid].editing = !this.annotationUIStates[cell._uuid].editing
        this.updateEditingCells()
370
        this.$root.$emit('mosys_saveScrollPosition')
Mathias Bär's avatar
Mathias Bär committed
371
      },
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387

      handleCellClick (event, cell) {
        this.annotationUIStates[cell._uuid].selected = !this.annotationUIStates[cell._uuid].selected
        this.updateSelectedCells()
      },
      handleCellDragStart (event, annotation) {
        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
Mathias Bär's avatar
Mathias Bär committed
388
          this.annotationUIStates[annotation._uuid].beingDragged = true
389
390
391
392
393
394
395
396
397
        }
        this.tmpObjects.push(annotation)
      },
      handleCellDragEnd (event, annotation) {
        this.annotationUIStates[annotation._uuid].beingDragged = false
      },
      async handleCellContextMenuDelete (event, annotation) {
        this.annotationUIStates[annotation._uuid].selected = false
        this.updateSelectedCells()
Mathias Bär's avatar
Mathias Bär committed
398
        this.updateEditingCells()
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
        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
442
443
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
      },
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
      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
461
        }
462
463
464
465
466
467
468
469
470
        catch (e) { /* dialog canceled */ }
      },

      //
      // GRID CONTEXT MENU
      //

      handleGridContextMenu (event) {
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
Anton's avatar
Intial  
Anton committed
471
472
473
      },
      async handleGridContextMenuInsertColumnLeft () {
        let position = this.contextMenuClickPosition
474
475
476
477
478
479
480
481
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[0] >= position.x) {
            parsed.xywh[0] += 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
482
483
          }
        }
484
        this.grid.config.columns += 1
Anton's avatar
Intial  
Anton committed
485
486
487
488
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteColumn () {
        let position = this.contextMenuClickPosition
489
490
491
492
493
494
495
496
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[0] > position.x) {
            parsed.xywh[0] -= 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
497
498
          }
        }
499
        this.grid.config.columns -= 1
Anton's avatar
Intial  
Anton committed
500
501
502
503
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuInsertRowAbove () {
        let position = this.contextMenuClickPosition
504
505
506
507
508
509
510
511
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[1] >= position.y) {
            parsed.xywh[1] += 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
512
513
          }
        }
514
        this.grid.config.rows += 1
Anton's avatar
Intial  
Anton committed
515
516
517
518
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteRow () {
        let position = this.contextMenuClickPosition
519
520
521
522
523
524
525
526
        for (let annotation of this.annotations) {
          const parsed = annotation.target.selector.parse()
          if (parsed.xywh[1] > position.y) {
            parsed.xywh[1] -= 1
            annotation.target.selector.value = parsed
            await this.$store.dispatch('annotations/patch', [annotation.id, {
              target: { selector: { value: parsed } }
            }])
Anton's avatar
Intial  
Anton committed
527
528
          }
        }
529
        this.grid.config.rows -= 1
Anton's avatar
Intial  
Anton committed
530
531
        await this.updateGridMetadataStore()
      },
532
533
534
535
536

      //
      // NAVIGATION
      //

Anton's avatar
Intial  
Anton committed
537
      handleGridButtonClickEdit () {
538
        this.$store.commit('mosys/toggleSources')
Anton's avatar
Intial  
Anton committed
539
      },
540
541
542
543
544

      //
      // GRID HELPERS
      //

Anton's avatar
Anton committed
545
546
      getGridPositionForEvent (event, offset = { x: 0, y: 0 }) {
        offset = { x: 0, y: 0 } // TODO: remove quick fix
Anton's avatar
Intial  
Anton committed
547
548
549
550
551
        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
552
        return { x: x, y: y, ox: ox, oy: oy }
Anton's avatar
Intial  
Anton committed
553
      },
554
555
556
557
      updateGridDimensions (size) {
        if (!this.grid || !this.grid.config) return

        let elWidth = size ? size.width : this.$el.offsetWidth
558
        // let elHeight = size ? size.height : this.$el.offsetHeight
559
        let cellSizeRatio = this.grid.config.ratio
560
561
        // let gridHeight = elHeight
        let gridHeight = this.$el.offsetHeight
562
        let cellHeight = gridHeight / this.grid.config.rows
Anton's avatar
Intial  
Anton committed
563
        let cellWidth = elWidth / Math.round(elWidth / (cellHeight * cellSizeRatio))
564
        let gridWidth = cellWidth * this.grid.config.columns
Anton's avatar
Intial  
Anton committed
565
        let cellsPerWidth = elWidth / cellWidth
566
        let cellWidthMini = elWidth / this.grid.config.columns
Anton's avatar
Intial  
Anton committed
567
568
569
570
571
572
573
574
575
576
577
578
579
        let gridHeightMini = cellWidthMini / cellSizeRatio
        this.gridDimensions = {
          full: {
            width: gridWidth,
            height: gridHeight,
            cell: {
              width: cellWidth,
              height: cellHeight
            },
            cells_per_width: cellsPerWidth
          },
          mini: {
            width: elWidth,
580
            height: gridHeightMini * this.grid.config.rows,
Anton's avatar
Intial  
Anton committed
581
582
583
584
585
586
587
            cell: {
              width: cellWidthMini,
              height: gridHeightMini
            }
          }
        }
      },
588
589
      getAnnotationStyle (annotation) {
        const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
590
        return {
591
592
593
594
          '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
595
        }
Mathias Bär's avatar
Mathias Bär committed
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
      },
      getAnnotationClasses (uuid, which) {
        if (which === 'editing') {
          if (this.annotationUIStates[uuid].editing) {
            return 'bg-primary text-white'
          }
          else {
            return 'bg-grey'
          }
        }
        if (which === 'cell-item') {
          return {
            selected: this.annotationUIStates[uuid] ? this.annotationUIStates[uuid].selected : false,
            editing: this.annotationUIStates[uuid] ? this.annotationUIStates[uuid].editing : false
          }
        }
        else return {}
613
614
615
616
      },
      resetScrollPosition () {
        console.log('reset', this.scrollPositionCache, this.$el.scrollLeft)
        this.$el.scrollLeft = this.scrollPositionCache
Anton's avatar
Intial  
Anton committed
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
      }
    }
  }
</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

Mathias Bär's avatar
Mathias Bär committed
644
645
646
647
      .edit-button
        display: block

    &.editing
648
      background-color lightpink
Mathias Bär's avatar
Mathias Bär committed
649
650
651
652

      .edit-button
        display: block

Anton's avatar
Intial  
Anton committed
653
654
655
    &.selected
      background-color lightpink

Mathias Bär's avatar
Mathias Bär committed
656
657
658
    .edit-button
      display: none

Anton's avatar
Intial  
Anton committed
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
    .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>