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

  div.cell-grid-container
4

5
    q-window-resize-observable(@resize="updateGridDimensions")
Anton's avatar
Intial  
Anton committed
6

christianrhansen's avatar
christianrhansen committed
7
8
    // ------------------------------------------------------------------------------------------ cached new cell helper
    // q-page-sticky.z-top(v-touch-pan="moveCachedCell", v-if="cachedNewCell", position="top-right")
christianrhansen's avatar
christianrhansen committed
9
10
11
    //
      .fixed-top-right.z-top(v-touch-pan="moveCachedCell", v-if="cachedNewCell",
      // :style="{top: touch.position.top - 16 - 16 + 'px'}")
12
    // .fixed-top.z-top(v-touch-pan="moveCachedCell", v-if="cachedNewCell")
christianrhansen's avatar
christianrhansen committed
13
14
    #cell-helper.fixed-top.z-top.bg-dark.text-white.transition(:class="{'show': cachedNewCell}")
      q-item.q-px-md.q-py-none.full-height
15
        q-item-main
christianrhansen's avatar
christianrhansen committed
16
          | Touch anywhere to place the new cell.
17
        q-item-side
christianrhansen's avatar
christianrhansen committed
18
19
          q-btn.bg-transparent.text-white.on-right(@click.native="$store.commit('mosys/cacheNewCell', undefined)", round, flat)
            q-icon(name="clear")
20
21

    // ------------------------------------------------------------------------------------------------------------ grid
22
      @click="event => {addMobileCell(event)}",
Anton's avatar
Intial  
Anton committed
23
    div.cell-grid(
24
    v-touch-pan="panGrid",
25
26
27
28
29
30
    @dragenter="handleGridDragOver",
    @dragover="handleGridDragOver",
    @dragleave="handleGridDragEnd",
    @drop="handleGridDrop",
    @contextmenu="handleGridContextMenu",
    :style="gridStyle")
Anton's avatar
Intial  
Anton committed
31

32
33
      // context menu grid (desktop only)
      q-context-menu.desktop-only(ref="gridmenu")
Anton's avatar
Intial  
Anton committed
34
35
        q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
          q-item(
36
37
38
39
40
          v-for="action in gridContextMenuActions",
          :key="action.label",
          v-close-overlay,
          @click.native="event => {action.handler(event)}")
            q-item-main(:label="action.label")
Anton's avatar
Intial  
Anton committed
41
42
43

      template(v-if="!resizingGrid")

44
        template(v-for="(annotation, index) in annotations")
christianrhansen's avatar
christianrhansen committed
45
46

          //----- cell
Anton's avatar
Intial  
Anton committed
47
          .cell-item(
48
          v-if="!annotationUIStates[annotation._uuid] || !annotationUIStates[annotation._uuid].beingDragged",
49
          v-touch-hold="event => {cellHold(event, annotation)}",
50
51
52
53
54
55
56
57
58
59
60
61
          draggable="true",
          @click.prevent="event => {handleCellTouch(event, annotation)}",
          @dragstart="event => {handleCellDragStart(event, annotation)}",
          @dragend="event => {handleCellDragEnd(event, annotation)}",
          @contextmenu="handleCellContextMenu",
          :style="getAnnotationStyle(annotation)",
          :class="getAnnotationClasses(annotation._uuid, 'cell-item')",
          :key="`cell-${index}`")

            //----- edit-/close-button
            // TODO: find a more elegant solution
            .desktop-only
Mathias Bär's avatar
Mathias Bär committed
62
              q-btn.edit-button.absolute-top-right(
63
64
65
66
67
68
69
70
71
              @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"
              )
            .mobile-only
              q-btn.edit-button.absolute.fit.bg-transparent(
              @click.prevent="event => {touchMobileCell(event, annotation)}", flat)

72
            //----- selecting cells disabled because it has no use currently
73
74
75
76
77
78
79
80
            // switch with cell component below to re-enable it
            //cell(
              @click.native.prevent="event => {handleCellClick(event, annotation)}", :annotation="annotation", :preview="true")
            cell(
            :annotation="annotation",
            :preview="true"
            )

christianrhansen's avatar
christianrhansen committed
81
            //----- resize-handler (desktop only)
82
            .desktop-only.cell-item-resize-handle(
83
84
85
86
87
88
            draggable="true",
            @dragstart="event => {handleCellResizerDragStart(event, annotation)}",
            @dragend="event => {handleCellResizerDragEnd(event, annotation)}",
            @dragexit="event => {handleCellResizerDragEnd(event, annotation)}")
              q-icon(name="network cell")

89
90
91
            //----- context menu for cells (desktop only)
            // TODO: needs revision
            q-context-menu.desktop-only
92
93
94
95
96
97
98
              q-list(link, separator, no-border, style="min-width: 150px; max-height: 300px;")
                q-item(
                v-for="action in cellContextMenuActions",
                :key="action.label",
                v-close-overlay,
                @click.native="event => {action.handler(event, annotation)}")
                  q-item-main(:label="action.label")
Anton's avatar
Intial  
Anton committed
99

100
        //----- temporary cell when pulling a new cell into grid (desktop)
101
        template(v-for="(tmpCell, index) in tmpObjects")
102
          .cell-item.cell-item-tmp(:style="getAnnotationStyle(tmpCell)", :key="`cell-tmp-${index}`")
Anton's avatar
Intial  
Anton committed
103
104
            cell(:cell="tmpCell")

105
        //----- temporary cell (mobile)
106
        .cell-item.cell-item-tmp-mobile(v-if="mobileTempCell.show && cachedNewCell", :style="mobileTempCellStyle(mobileTempCell)")
107
108
          // cell(:cell="tmpCell")

christianrhansen's avatar
christianrhansen committed
109
      // ---------------------------------------------------------------------------------------------------------------
Anton's avatar
Intial  
Anton committed
110
      template(v-else)
111
        .cell-item(:style="getAnnotationStyle({x:0,y:0,width:1,height:1})", key="cell-grid-resizer")
Anton's avatar
Intial  
Anton committed
112
          div.cell-item-resize-handle(
113
114
115
116
117
          draggable="true",
          @dragstart="event => {handleGridResizerDragStart(event)}",
          @dragend="event => {handleGridResizerDragEnd(event)}",
          @dragexit="event => {handleGridResizerDragEnd(event)}")
            q-icon(name="network cell")
Anton's avatar
Intial  
Anton committed
118

Mathias Bär's avatar
Mathias Bär committed
119
      //template(v-if="!isMobile")
120
121
122
123
124
        .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
125
      //template(v-if="isMobile")
Mathias Bär's avatar
Mathias Bär committed
126
127
128
129
130
        .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
131

christianrhansen's avatar
christianrhansen committed
132
    // ------------------------------------------------------------------------------------------ edit box (mobile only)
133

christianrhansen's avatar
christianrhansen committed
134
135
    q-page-sticky.z-top.q-mb-md.backdrop-filter.shadow-1.q-mx-md.overflow-hidden.round-borders(
    v-if="showEditingCells", position="bottom-right")
136
137
      div.full-width

christianrhansen's avatar
christianrhansen committed
138
        //----- "edit content"-buttons
139
140
141
        div#edit-content-mobile(v-if="editMode === 'edit'")
          grid-editor-editing-cells-mobile

christianrhansen's avatar
christianrhansen committed
142
        //----- "move"-buttons
christianrhansen's avatar
christianrhansen committed
143
        div(v-if="editMode === 'move'", :class="{'q-pa-md' : editMode}")
144
          .text-center
145
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, 0, -1)", round, flat, size="md")
146
147
              q-icon.rotate-90(name="keyboard_backspace")
          .text-center
148
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, -1, 0)", round, flat, size="md")
149
              q-icon(name="keyboard_backspace")
150
            q-btn.invisible.text-dark(round, flat, size="md")
151
              q-icon.flip-vertical(name="photo_size_select_small")
152
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, 1, 0)", round, flat, size="md")
153
154
              q-icon.rotate-180(name="keyboard_backspace")
          .text-center
155
            q-btn.edit-cell-button(@click="mobileCellMove(mobileSelectedCell, 0, 1)", round, flat, size="md")
156
157
              q-icon.rotate-270(name="keyboard_backspace")

christianrhansen's avatar
christianrhansen committed
158
        //----- "resize"-buttons
christianrhansen's avatar
christianrhansen committed
159
        div(v-if="editMode === 'resize'", :class="{'q-pa-md' : editMode}")
160
          .text-center
161
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, 0, -1)", round, flat, size="md")
162
163
              q-icon(name="remove")
          .text-center
164
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, -1, 0)", round, flat, size="md")
165
              q-icon(name="remove")
166
            q-btn.invisible.text-dark(round, flat, size="md")
167
              q-icon.rotate-45(name="zoom_out_map")
168
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, 1, 0)", round, flat, size="md")
169
170
              q-icon(name="add")
          .text-center
171
            q-btn.edit-cell-button(@click="mobileCellResize(mobileSelectedCell, 0, 1)", round, flat, size="md")
172
173
              q-icon(name="add")

christianrhansen's avatar
christianrhansen committed
174
        //----- "delete"-button
175
176
177
178
179
        div(v-if="editMode === 'delete'", :class="{'q-pa-md' : editMode}")
          .text-center
            .text-dark
              | {{ $t('labels.delete_cell') }}
            .q-mt-md
180
              q-btn.edit-cell-button(@click="event => {handleCellContextMenuDelete(event, mobileSelectedCell)}",
181
              flat, round, size="md")
182
183
                q-icon(name="check")

184
185
      div.row.text-center.text-dark.round-borders.full-width
        .col
186
          q-btn.full-width(@click="setEditMode('edit')", :class="{'edit-cell-button' : editMode === 'edit'}", flat, no-ripple)
187
188
            q-icon(name="edit")
        .col
189
          q-btn.full-width(@click="setEditMode('resize')", :class="{'edit-cell-button' : editMode === 'resize'}", flat, no-ripple)
190
191
            q-icon.flip-vertical(name="photo_size_select_small")
        .col
192
          q-btn.full-width(@click="setEditMode('move')", :class="{'edit-cell-button' : editMode === 'move'}", flat, no-ripple)
193
194
            q-icon(name="open_with")
        .col
195
          q-btn.full-width(@click="setEditMode('delete')", :class="{'edit-cell-button' : editMode === 'delete'}", flat, no-ripple)
196
197
            q-icon(name="delete")

Anton's avatar
Intial  
Anton committed
198
199
200
201
</template>

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

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

  export default {
    components: {
211
212
      Cell,
      GridEditorEditingCellsMobile
Anton's avatar
Intial  
Anton committed
213
    },
214
    props: ['gridUuid', 'tabsAreOpen'],
Anton's avatar
Intial  
Anton committed
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    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,
240
        annotations: undefined,
241
        tmpObjects: [],
242
        annotationUIStates: {},
Anton's avatar
Anton committed
243
        gridDimensions: { gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0 },
Anton's avatar
Intial  
Anton committed
244
        contextMenuClickPosition: {},
245
        resizingGrid: false,
christianrhansen's avatar
christianrhansen committed
246
        mobileSelectedCell: undefined,
247
        touch: {position: {top: undefined, left: undefined}},
248
        isMobile: this.$q.platform.is.mobile,
249
        mobileTempCell: {x: 0, y: 0, width: 1, height: 1, show: false}
Anton's avatar
Intial  
Anton committed
250
251
      }
    },
252
253
    computed: {
      ...mapGetters({
christianrhansen's avatar
christianrhansen committed
254
        cachedNewCell: 'mosys/getNewCell',
255
        user: 'auth/getUserState',
256
        // isMobile: 'globalSettings/getIsMobile',
Mathias Bär's avatar
Mathias Bär committed
257
        // editingCells: 'mosys/getEditingCells'
258
        showEditingCells: 'mosys/getShowEditingCells',
259
260
        scrollPositionCache: 'mosys/getScrollPositionCache',
        editMode: 'mosys/getEditMode'
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
      }),
      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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
      },
      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>")`
        }
300
301
      }
    },
Anton's avatar
Intial  
Anton committed
302
303
    async mounted () {
      await this.fetchData()
304
      this.resetScrollPosition()
Anton's avatar
Intial  
Anton committed
305
306
    },
    watch: {
Anton's avatar
Anton committed
307
      annotations () {
308
        this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
309
310
311
312
313
314
      },
      gridMetadata () {
        this.updateGridDimensions()
      },
      async gridUuid () {
        await this.fetchData()
Mathias Bär's avatar
Mathias Bär committed
315
316
      },
      showEditingCells (val) {
317
        // console.log('show editing cells', val)
Mathias Bär's avatar
Mathias Bär committed
318
319
320
        if (val === false) {
          this.updateAnnotationUIStates()
        }
321
322
323
      },
      tabsAreOpen () {
        this.resetScrollPosition()
Anton's avatar
Intial  
Anton committed
324
325
326
      }
    },
    methods: {
327
      panGrid (obj) {
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
        if (this.cachedNewCell) {
          console.log('hhhhhhhhhhhhhhhhh', obj)
          let res = this.getGridPositionForEvent(obj)

          if (obj.isFirst) {
            this.mobileTempCell.show = true
            this.mobileTempCell.x = res.x
            this.mobileTempCell.y = res.y
          }

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

          if (obj.isFinal) {
            this.addMobileCell(obj)
            this.mobileTempCell.show = false
          }
345
        }
346
      },
347
      async cellHold (event, annotation) {
348
349
        this.$store.commit('mosys/setEditingCells', '')

christianrhansen's avatar
christianrhansen committed
350
        /*
351
352
353
354
355
        this.$q.notify({
          message: 'Cell was cloned.',
          color: 'dark',
          textColor: 'white'
        })
christianrhansen's avatar
christianrhansen committed
356
        */
357
358
359
360
361
362
363
364
365

        const _cell = await this.$store.dispatch('cells/get', annotation.body.source.id)
        const resourceCell = {
          data: { content: '' },
          config: {},
          component: _cell.component
        }
        this.$store.commit('mosys/cacheNewCell', resourceCell)
      },
christianrhansen's avatar
christianrhansen committed
366
367
      moveCachedCell (obj) {
        this.touch.position = {top: obj.position.top, left: obj.position.left}
368
369
370
371
372
373
374
375
376
        /*
        if (obj.isFinal) {
          console.log(obj.isFinal)
          console.log(window.event)
          console.log(this.touch.position)
          console.log('--->->->', this.getGridPositionForEvent(event))
        }
        */
        // console.log('--->>>', this.getGridPositionForEvent(event))
christianrhansen's avatar
christianrhansen committed
377
      },
378
379
      addMobileCell (event) {
        // console.log('addMobileCell()', event, annotation)
christianrhansen's avatar
christianrhansen committed
380
        if (this.cachedNewCell) {
381
          this.handleGridDrop(event)
christianrhansen's avatar
christianrhansen committed
382
383
384
          this.$store.commit('mosys/cacheNewCell', undefined)
        }
      },
385
386
387
388
389
390
391
392
393
394
395
396
397
398
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
      touchMobileCell (event, cell) {
        Object.keys(this.annotationUIStates).filter((k) => {
          if (k === cell._uuid && this.annotationUIStates[k].editing) console.log(k, cell._uuid)
          else this.annotationUIStates[k].editing = false
        })
        this.annotationUIStates[cell._uuid].editing = !this.annotationUIStates[cell._uuid].editing
        this.updateEditingCells()
        this.$root.$emit('mosys_saveScrollPosition')
        // this.handleCellEditClick(event, annotation)
      },
      async mobileCellMove (annotation, _x, _y) {
        let
          parsed = annotation.target.selector.parse(),
          sliced = parsed.xywh.slice(0, 2),
          x = sliced[0],
          y = sliced[1]
        x += _x
        y += _y
        let target = this.grid.get2DArea([x, y], parsed.xywh.slice(2))
        annotation.target.selector.value = target.selector.value
        await this.$store.dispatch('annotations/patch', [annotation.id, {
          target: {
            selector: { value: target.selector.value }
          }
        }])
      },
      handleCellTouch (event, annotation) {
        console.log(event, annotation)
        this.mobileSelectedCell = annotation
      },
      async mobileCellResize (annotation, width, height) {
        let
          parsed = annotation.target.selector.parse(),
          [x, y, w, h] = parsed.xywh
        w += width
        h += height
        const value = { xywh: [x, y, w, h] }
        annotation.target.selector.value = value

        await this.$store.dispatch('annotations/patch', [annotation.id, { target: { selector: { value } } }])
      },
      setEditMode (mode) {
        this.$store.commit('mosys/setEditMode', mode)
      },
429
430
431
432
      //
      // DATA
      //

Anton's avatar
Intial  
Anton committed
433
434
435
      async fetchData () {
        if (this.gridUuid) {
          this.grid = await this.$store.dispatch('maps/get', this.gridUuid)
436
437
438
439
440
441
442
443
          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
444
          this.updateGridDimensions()
445
446
447
448
449
450
          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
451
452
        }
      },
453
454
455
      async updateGridMetadataStore () {
        await this.$store.dispatch('maps/patch', [this.grid.id, { config: this.grid.config }])
        this.updateGridDimensions()
Anton's avatar
Intial  
Anton committed
456
      },
457
458
459
460
461
      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
462
463
          // return _this.cells.find(c => c._uuid === k)
          return _this.annotations.find(c => c._uuid === k)
464
        })
christianrhansen's avatar
christianrhansen committed
465
        console.log('wwww', selectedCells)
466
        this.$store.commit('mosys/setSelectedCells', selectedCells)
Anton's avatar
Intial  
Anton committed
467
      },
Mathias Bär's avatar
Mathias Bär committed
468
469
470
471
472
473
474
475
      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)
        })
476
477
478
479
480
481
482
        /*
        console.log('this.annotationUIStates: ', this.annotationUIStates)
        console.log('editingCells: ', editingCells)
        if (this.isMobile) {
          console.log('MOBILE')
        }
        */
Mathias Bär's avatar
Mathias Bär committed
483
484
        this.$store.commit('mosys/setEditingCells', editingCells)
      },
485
486
487
488
489
      updateAnnotationUIStates () {
        let newAnnotationUIStates = {}
        this.annotations.forEach(a => {
          newAnnotationUIStates[a._uuid] = {
            selected: false,
Mathias Bär's avatar
Mathias Bär committed
490
            editing: false,
491
            beingResized: false,
Mathias Bär's avatar
Mathias Bär committed
492
            beginDragged: false,
493
            annotation: a
Anton's avatar
Intial  
Anton committed
494
          }
495
496
        })
        this.annotationUIStates = newAnnotationUIStates
Anton's avatar
Intial  
Anton committed
497
        this.updateSelectedCells()
Mathias Bär's avatar
Mathias Bär committed
498
        this.updateEditingCells()
499
      },
500
501
502
503
504

      //
      // GRID DRAG & DROP HANDLERS
      //

505
      async handleGridDragOver (event) {
Anton's avatar
Intial  
Anton committed
506
507
508
        let _this = this
        if (this.resizingGrid) {
          const position = this.getGridPositionForEvent(event)
509
510
          this.grid.config.ratio = position.ox / (position.oy * 1.0)
          await this.updateGridMetadataStore()
Anton's avatar
Intial  
Anton committed
511
512
        }
        else {
513
514
          let annotation = this.annotations.filter(annotation => {
            if (!_this.annotationUIStates[annotation._uuid]) return false
Mathias Bär's avatar
Mathias Bär committed
515
            return _this.annotationUIStates[annotation._uuid].beingDragged ||
516
              _this.annotationUIStates[annotation._uuid].beingResized
Anton's avatar
Intial  
Anton committed
517
518
          }).shift()
          let offset, position
519
520
521
522
          if (!annotation) {
            annotation = {
              target: this.grid.get2DArea([1, 1], [1, 1])
            }
Anton's avatar
Intial  
Anton committed
523
524
525
            position = this.getGridPositionForEvent(event)
          }
          else {
526
            offset = this.annotationUIStates[annotation._uuid].draggingOffset
Anton's avatar
Intial  
Anton committed
527
528
            position = this.getGridPositionForEvent(event, offset)
          }
529
530
          if (!this.tmpObjects.length) this.tmpObjects.push(annotation)
          const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
531
          if (event.dataTransfer.types.includes('text/plain')) {
532
533
            parsed.xywh[0] = position.x
            parsed.xywh[1] = position.y
Anton's avatar
Intial  
Anton committed
534
535
536
            event.preventDefault()
          }
          else {
537
538
            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
539
          }
540
          annotation.target.selector.value = parsed
Anton's avatar
Intial  
Anton committed
541
542
543
        }
      },
      handleGridDragEnd () {
544
        this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
545
      },
546
      async handleGridDrop (event) {
547
548
549
550
551
        let dropData
        if (event.dataTransfer) dropData = event.dataTransfer.getData('text/plain')
        if (!dropData) dropData = JSON.stringify(this.cachedNewCell)
        // console.log('handleGridDrop - event', event)
        // console.log('handleGridDrop - dropData', dropData)
552
553
        if (dropData) {
          dropData = JSON.parse(dropData)
554
          let annotation = this.annotations.find(a => a.id === dropData.id)
555
556
557
558
559
          const { x, y } = this.getGridPositionForEvent(
            event,
            annotation ? this.annotationUIStates[annotation._uuid].draggingOffset : undefined
          )
          if (annotation) {
560
561
562
            const
              parsed = annotation.target.selector.parse(),
              target = this.grid.get2DArea([x, y], parsed.xywh.slice(2))
563
564
565
566
567
568
            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
569
570
          }
          else {
571
572
            if (this.mobileTempCell.x) console.log('this.mobileTempCell', this.mobileTempCell)
            let test = this.grid.get2DArea([this.mobileTempCell.x, this.mobileTempCell.y], [this.mobileTempCell.width, this.mobileTempCell.height])
573
574
575
576
577
578
579
580
581
582
583
            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
                }
              },
584
585
              // target: this.grid.get2DArea([x, y], [1, 1])
              target: test
586
            })
587
            this.annotations.push(annotation)
Anton's avatar
Anton committed
588
            this.updateAnnotationUIStates()
Anton's avatar
Intial  
Anton committed
589
          }
590

591
          this.tmpObjects = []
Anton's avatar
Intial  
Anton committed
592
593
          event.preventDefault()
        }
594
        this.mobileTempCell = {x: undefined, y: undefined, width: undefined, height: undefined}
Anton's avatar
Intial  
Anton committed
595
      },
596
597
598
599

      //
      // CELL DRAG & DROP HANDLERS
      //
Mathias Bär's avatar
Mathias Bär committed
600
601
602
      handleCellEditClick (event, cell) {
        this.annotationUIStates[cell._uuid].editing = !this.annotationUIStates[cell._uuid].editing
        this.updateEditingCells()
603
        this.$root.$emit('mosys_saveScrollPosition')
Mathias Bär's avatar
Mathias Bär committed
604
      },
605
606
607
608
609
      handleCellClick (event, cell) {
        this.annotationUIStates[cell._uuid].selected = !this.annotationUIStates[cell._uuid].selected
        this.updateSelectedCells()
      },
      handleCellDragStart (event, annotation) {
christianrhansen's avatar
christianrhansen committed
610
        console.log('§§§§§§§§', annotation)
611
612
613
614
615
616
617
618
619
620
        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
621
          this.annotationUIStates[annotation._uuid].beingDragged = true
622
623
624
625
626
627
628
629
630
        }
        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
631
        this.updateEditingCells()
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
665
666
667
668
669
670
671
672
673
674
        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
675
676
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
      },
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
      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
694
        }
695
696
697
698
699
700
701
702
703
        catch (e) { /* dialog canceled */ }
      },

      //
      // GRID CONTEXT MENU
      //

      handleGridContextMenu (event) {
        this.contextMenuClickPosition = this.getGridPositionForEvent(event)
Anton's avatar
Intial  
Anton committed
704
705
706
      },
      async handleGridContextMenuInsertColumnLeft () {
        let position = this.contextMenuClickPosition
707
708
709
710
711
712
713
714
        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
715
716
          }
        }
717
        this.grid.config.columns += 1
Anton's avatar
Intial  
Anton committed
718
719
720
721
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteColumn () {
        let position = this.contextMenuClickPosition
722
723
724
725
726
727
728
729
        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
730
731
          }
        }
732
        this.grid.config.columns -= 1
Anton's avatar
Intial  
Anton committed
733
734
735
736
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuInsertRowAbove () {
        let position = this.contextMenuClickPosition
737
738
739
740
741
742
743
744
        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
745
746
          }
        }
747
        this.grid.config.rows += 1
Anton's avatar
Intial  
Anton committed
748
749
750
751
        await this.updateGridMetadataStore()
      },
      async handleGridContextMenuDeleteRow () {
        let position = this.contextMenuClickPosition
752
753
754
755
756
757
758
759
        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
760
761
          }
        }
762
        this.grid.config.rows -= 1
Anton's avatar
Intial  
Anton committed
763
764
        await this.updateGridMetadataStore()
      },
765
766
767
768
769

      //
      // NAVIGATION
      //

Anton's avatar
Intial  
Anton committed
770
      handleGridButtonClickEdit () {
771
        this.$store.commit('mosys/toggleSources')
Anton's avatar
Intial  
Anton committed
772
      },
773
774
775
776
777

      //
      // GRID HELPERS
      //

Anton's avatar
Anton committed
778
      getGridPositionForEvent (event, offset = { x: 0, y: 0 }) {
779
        // console.log('###', event)
Anton's avatar
Anton committed
780
        offset = { x: 0, y: 0 } // TODO: remove quick fix
781
782
783
784
785
786
787

        let _clientX, _clientY

        if (event.clientX && event.clientY) {
          _clientX = event.clientX
          _clientY = event.clientY
        }
788
        if (event.position) {
789
790
791
792
          _clientX = event.position.left
          _clientY = event.position.top
        }

Anton's avatar
Intial  
Anton committed
793
        const elContainerBoundingBox = this.$el.getBoundingClientRect()
794
795
796
797
        const ox = _clientX + this.$el.scrollLeft - elContainerBoundingBox.x - offset.x
        const oy = _clientY + this.$el.scrollTop - elContainerBoundingBox.y - offset.y
        // const ox = event.clientX + this.$el.scrollLeft - elContainerBoundingBox.x - offset.x
        // const oy = event.clientY + this.$el.scrollTop - elContainerBoundingBox.y - offset.y
Anton's avatar
Intial  
Anton committed
798
799
        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
800
        return { x: x, y: y, ox: ox, oy: oy }
Anton's avatar
Intial  
Anton committed
801
      },
802
803
804
805
      updateGridDimensions (size) {
        if (!this.grid || !this.grid.config) return

        let elWidth = size ? size.width : this.$el.offsetWidth
806
        // let elHeight = size ? size.height : this.$el.offsetHeight
807
        let cellSizeRatio = this.grid.config.ratio
808
809
        // let gridHeight = elHeight
        let gridHeight = this.$el.offsetHeight
810
        let cellHeight = gridHeight / this.grid.config.rows
Anton's avatar
Intial  
Anton committed
811
        let cellWidth = elWidth / Math.round(elWidth / (cellHeight * cellSizeRatio))
812
        let gridWidth = cellWidth * this.grid.config.columns
Anton's avatar
Intial  
Anton committed
813
        let cellsPerWidth = elWidth / cellWidth
814
        let cellWidthMini = elWidth / this.grid.config.columns
Anton's avatar
Intial  
Anton committed
815
816
817
818
819
820
821
822
823
824
825
826
827
        let gridHeightMini = cellWidthMini / cellSizeRatio
        this.gridDimensions = {
          full: {
            width: gridWidth,
            height: gridHeight,
            cell: {
              width: cellWidth,
              height: cellHeight
            },
            cells_per_width: cellsPerWidth
          },
          mini: {
            width: elWidth,
828
            height: gridHeightMini * this.grid.config.rows,
Anton's avatar
Intial  
Anton committed
829
830
831
832
833
834
835
            cell: {
              width: cellWidthMini,
              height: gridHeightMini
            }
          }
        }
      },
836
837
838
839
      mobileTempCellStyle (obj) {
        console.log(obj)
        return {
          'grid-column-start': obj.x,
840
          'grid-column-end': `span ${obj.width}`,
841
          'grid-row-start': obj.y,
842
          'grid-row-end': `span ${obj.height}`
843
844
        }
      },
845
846
      getAnnotationStyle (annotation) {
        const parsed = annotation.target.selector.parse()
Anton's avatar
Intial  
Anton committed
847
        return {
848
849
850
851
          '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
852
        }
Mathias Bär's avatar
Mathias Bär committed
853
854
855
856
857
858
859
860
861
862
863
864
865
      },
      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,
866
867
            editing: this.annotationUIStates[uuid] ? this.annotationUIStates[uuid].editing : false,
            hover: !this.isMobile
Mathias Bär's avatar
Mathias Bär committed
868
869
870
          }
        }
        else return {}
871
872
873
874
      },
      resetScrollPosition () {
        console.log('reset', this.scrollPositionCache, this.$el.scrollLeft)
        this.$el.scrollLeft = this.scrollPositionCache
Anton's avatar
Intial  
Anton committed
875
876
877
878
879
880
      }
    }
  }
</script>

<style scoped lang="stylus">
881
882
  @import '~variables'

Anton's avatar
Intial  
Anton committed
883
884
885
886
887
  .cell-grid
    display grid
    background-color #eee

  .cell-item
888
    border-radius .75rem
889
    backdrop-filter blur(3px)
Anton's avatar
Intial  
Anton committed
890
891
892

    &
      background-color rgba(0,0,0,0.2)
893
      border 1px solid rgba(0, 0, 0, .2)
Anton's avatar
Intial  
Anton committed
894
895
896
897
898
899
900
901
902
903
      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
904
      /*background-color lightblue*/
Anton's avatar
Intial  
Anton committed
905

Mathias Bär's avatar
Mathias Bär committed
906
907
908
909
      .edit-button
        display: block

    &.editing
910
911
      /*background-color lightpink*/
      background rgba(255, 0, 0, .3)
Mathias Bär's avatar
Mathias Bär committed
912
913
914
915

      .edit-button
        display: block

Anton's avatar
Intial  
Anton committed
916
917
918
    &.selected
      background-color lightpink

919
920
921
922
    &.hover
      &:hover
        background-color lightblue

Mathias Bär's avatar
Mathias Bär committed
923
924
925
    .edit-button
      display: none

Anton's avatar
Intial  
Anton committed
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
    .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

941
942
943
  .backdrop-filter
    backdrop-filter blur(6px)
    background-color rgba(255, 255, 255, .3)
944
    /*border-radius .5rem*/
945
946
947
948
949

  #edit-content-mobile
    width calc(100vw - 32px)
    max-height calc(calc(100vh - 60px - 16px - 16px) / 2)
    overflow-y scroll
christianrhansen's avatar
christianrhansen committed
950
951
952
953
954
955

  #cell-helper
    height 59px
    top -60px
    &.show
      top 0px
956
957

  .cell-item-tmp-mobile
958
959
    background-color rgba(0, 0, 255, .15)
    backdrop-filter blur(0px)
960
961
962
963
964

  .edit-cell-button
    border 0px solid #333
    background-color rgba(0, 0, 0, .1)
    color $dark
Anton's avatar
Intial  
Anton committed
965
</style>