Commit 7809a92e authored by Anton's avatar Anton
Browse files

Fix remaining grid editor functionality, remove obsolete code, refactor

parent aa964150
<template lang="pug">
div.cell-grid-container
q-window-resize-observable(@resize="updateGridDimensions")
div.cell-grid(
@dragenter="handleGridDragOver",
......@@ -71,7 +72,6 @@
</template>
<script>
import { ObjectUtil } from 'mbjs-utils'
import Cell from './Cell'
import { userHasFeature } from 'mbjs-quasar/src/lib'
import { mapGetters } from 'vuex'
......@@ -87,10 +87,6 @@
data () {
return {
gridContextMenuActions: {
// add_cell: {
// label: 'Add Cell',
// handler: this.handleGridContextMenuAddCell
// },
insert_column_left: {
label: 'Insert Column Left',
handler: this.handleGridContextMenuInsertColumnLeft
......@@ -114,12 +110,9 @@
},
grid: undefined,
annotations: undefined,
gridMetadata: {},
cells: [],
tmpObjects: [],
annotationUIStates: {},
gridDimensions: { gridWidth: 0, gridHeight: 0, cellWidth: 0, cellHeight: 0 },
gridStyle: {},
contextMenuClickPosition: {},
resizingGrid: false
}
......@@ -130,10 +123,6 @@
}),
cellContextMenuActions () {
const actions = {
// edit: {
// label: 'Edit',
// handler: this.handleCellContextMenuEdit
// },
delete: {
label: 'Delete',
handler: this.handleCellContextMenuDelete
......@@ -154,15 +143,27 @@
}
}
return actions
},
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>")`
}
}
},
async mounted () {
window.addEventListener('resize', this.updateGridDimensions)
await this.fetchData()
},
beforeDestroy () {
window.removeEventListener('resize', this.updateGridDimensions)
},
watch: {
cells () {
this.updateAnnotationUIStates()
......@@ -175,6 +176,10 @@
}
},
methods: {
//
// DATA
//
async fetchData () {
if (this.gridUuid) {
this.grid = await this.$store.dispatch('maps/get', this.gridUuid)
......@@ -184,7 +189,6 @@
rows: 6,
ratio: 16 / 9.0
}
console.log('conf', this.grid.config)
await this.updateGridMetadataStore()
}
this.updateGridDimensions()
......@@ -197,89 +201,36 @@
this.updateAnnotationUIStates()
}
},
handleGridResizerDragStart (event) {
event.dataTransfer.setDragImage(nullImage, 0, 0)
},
async handleGridResizerDragEnd () {
await this.updateGridMetadataStore()
},
handleCellResizerDragStart (event, annotation) {
event.dataTransfer.setDragImage(nullImage, 0, 0)
this.annotationUIStates[annotation._uuid].beingResized = true
let tmpCell = this.getTmpCell(annotation)
this.tmpObjects.push(tmpCell)
},
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 } } }])
async updateGridMetadataStore () {
await this.$store.dispatch('maps/patch', [this.grid.id, { config: this.grid.config }])
this.updateGridDimensions()
},
handleCellClick (event, cell) {
this.annotationUIStates[cell._uuid].selected = !this.annotationUIStates[cell._uuid].selected
this.updateSelectedCells()
updateSelectedCells () {
const _this = this
let selectedCells = Object.keys(this.annotationUIStates).filter(k => {
return _this.annotationUIStates[k].selected
}).map(k => {
return _this.cells.find(c => c._uuid === k)
})
this.$store.commit('mosys/setSelectedCells', selectedCells)
},
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)
updateAnnotationUIStates () {
let newAnnotationUIStates = {}
this.annotations.forEach(a => {
newAnnotationUIStates[a._uuid] = {
selected: false,
beingResized: false,
annotation: a
}
this.annotationUIStates[annotation._uuid].draggingOffset = offset
this.annotationUIStates[annotation._uuid].beginDragged = true
}
this.tmpObjects.push(annotation)
},
handleCellDragEnd (event, cell) {
this.annotationUIStates[cell._uuid].beingDragged = false
},
// handleCellContextMenuClick () {
// },
// handleCellContextMenuEdit (/* event, cell */) {
// // this.$store.commit('mosys/showSources')
// // this.$store.commit('mosys/setSourcesTab', 'tab-default-cells')
// },
async handleCellContextMenuDelete (event, annotation) {
this.annotationUIStates[annotation._uuid].selected = false
})
this.annotationUIStates = newAnnotationUIStates
this.updateSelectedCells()
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)
},
handleCellContextMenu (event) {
this.contextMenuClickPosition = this.getGridPositionForEvent(event)
},
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
this.$store.dispatch('annotations/patch', [cell._uuid, { target: { styleClass: styleClass || null } }])
}
catch (e) { /* dialog canceled */ }
},
//
// GRID DRAG & DROP HANDLERS
//
async handleGridDragOver (event) {
let _this = this
if (this.resizingGrid) {
......@@ -307,7 +258,6 @@
if (!this.tmpObjects.length) this.tmpObjects.push(annotation)
const parsed = annotation.target.selector.parse()
if (event.dataTransfer.types.includes('text/plain')) {
annotation.target.selector.value = { xywh: [position.x, position.y] }
parsed.xywh[0] = position.x
parsed.xywh[1] = position.y
event.preventDefault()
......@@ -331,15 +281,16 @@
event,
annotation ? this.annotationUIStates[annotation._uuid].draggingOffset : undefined
)
const target = this.grid.get2DArea([x, y], [1, 1])
if (annotation) {
const
parsed = annotation.target.selector.parse(),
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 }
}
}])
console.debug('dropped existing annotation', annotation)
}
else {
const
......@@ -353,123 +304,191 @@
id: cell.id
}
},
target
target: this.grid.get2DArea([x, y], [1, 1])
})
console.debug('dropped new cell', cell, annotation)
this.annotations.push(annotation)
}
this.tmpObjects = []
// this.updateCellStore(cell)
event.preventDefault()
}
},
handleGridContextMenu (event) {
//
// CELL DRAG & DROP HANDLERS
//
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
this.annotationUIStates[annotation._uuid].beginDragged = true
}
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()
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) {
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'
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 } }])
}
let annotation = this.getGridCellAnnotation(newCell)
await this.$store.dispatch('annotations/post', annotation)
await this.fetchCellAnnotations()
catch (e) { /* dialog canceled */ }
},
//
// GRID CONTEXT MENU
//
handleGridContextMenu (event) {
this.contextMenuClickPosition = this.getGridPositionForEvent(event)
},
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)
await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
await this.fetchCellAnnotations()
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 } }
}])
}
}
this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { columns: this.gridMetadata.columns + 1 })
this.grid.config.columns += 1
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)
await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
await this.fetchCellAnnotations()
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 } }
}])
}
}
this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { columns: this.gridMetadata.columns - 1 })
this.grid.config.columns -= 1
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)
await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
await this.fetchCellAnnotations()
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 } }
}])
}
}
this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { rows: this.gridMetadata.rows + 1 })
this.grid.config.rows += 1
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)
await this.$store.dispatch('annotations/patch', [cell._uuid, annotation])
await this.fetchCellAnnotations()
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 } }
}])
}
}
this.gridMetadata = ObjectUtil.merge({}, this.gridMetadata, { rows: this.gridMetadata.rows - 1 })
this.grid.config.rows -= 1
await this.updateGridMetadataStore()
},
//
// NAVIGATION
//
handleGridButtonClickEdit () {
this.$store.commit('mosys/toggleSources')
},
updateSelectedCells () {
const _this = this
let selectedCells = Object.keys(this.annotationUIStates).filter(k => {
return _this.annotationUIStates[k].selected
}).map(k => {
return _this.cells.find(c => c._uuid === k)
})
this.$store.commit('mosys/setSelectedCells', selectedCells)
},
updateAnnotationUIStates () {
let newAnnotationUIStates = {}
this.annotations.forEach(a => {
newAnnotationUIStates[a._uuid] = {
selected: false,
beingResized: false,
annotation: a
}
})
this.annotationUIStates = newAnnotationUIStates
this.updateSelectedCells()
},
getTmpCell (cell, type = 'UIFeedback') {
return {
srcUuid: cell._uuid,
type: type,
x: cell.x,
y: cell.y,
width: cell.width,
height: cell.height
}
},
//
// GRID HELPERS
//
getGridPositionForEvent (event, offset = { x: 0, y: 0 }) {
offset = { x: 0, y: 0 } // TODO: remove quick fix
const elContainerBoundingBox = this.$el.getBoundingClientRect()
......@@ -479,9 +498,11 @@
const y = Math.ceil(oy / this.gridDimensions.full.cell.height)
return { x: x, y: y, ox: ox, oy: oy }
},
updateGridDimensions () {
let elWidth = this.$el.offsetWidth
let elHeight = this.$el.offsetHeight
updateGridDimensions (size) {
if (!this.grid || !this.grid.config) return
let elWidth = size ? size.width : this.$el.offsetWidth
let elHeight = size ? size.height : this.$el.offsetHeight
let cellSizeRatio = this.grid.config.ratio
let gridHeight = elHeight
let cellHeight = gridHeight / this.grid.config.rows
......@@ -509,99 +530,6 @@
}
}
}
// 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) {
cell._uuid = annotation._uuid
cell.id = annotation.id
cell.styleClass = annotation.target ? annotation.target.styleClass : undefined
cell.author = annotation.author
return cell
}
return null
}).filter(cell => cell)
this.updateAnnotationUIStates()
},
getGridCellAnnotation (cell) {
return {
body: {
type: '2DCell',
purpose: 'linking',
value: JSON.stringify(cell)
},
target: {
id: this.grid.id,
type: 'Map',
selector: {
conformsTo: 'Media',
type: 'FragmentSelector',
value: {
// xywh: [x, y, width, height]
}
}
}
}
},
getGridMetadataAnnotation (id, metadata) {
return {
body: {
type: '2DGridMetadata',
purpose: 'linking',