mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-05-02 08:00:03 +00:00
Resolve "Ordering of rows by drag and drop"
This commit is contained in:
parent
c299901fdf
commit
c9fbfb714f
25 changed files with 2501 additions and 642 deletions
web-frontend/modules/database/components/view/grid
|
@ -0,0 +1,260 @@
|
|||
<template>
|
||||
<div v-show="dragging" class="grid-view__row-dragging-container">
|
||||
<div
|
||||
class="grid-view__row-dragging"
|
||||
:style="{ width: width + 'px', top: draggingTop + 'px' }"
|
||||
></div>
|
||||
<div
|
||||
class="grid-view__row-target"
|
||||
:style="{ width: width + 'px', top: targetTop + 'px' }"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import gridViewHelpers from '@baserow/modules/database/mixins/gridViewHelpers'
|
||||
|
||||
export default {
|
||||
name: 'GridViewRowDragging',
|
||||
mixins: [gridViewHelpers],
|
||||
props: {
|
||||
table: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
view: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
primary: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
vertical: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Indicates if the user is dragging a row to another position.
|
||||
dragging: false,
|
||||
// The row object that is being dragged.
|
||||
row: null,
|
||||
// The top position of the row.
|
||||
rowTop: 0,
|
||||
// The row where the dragged row must be placed before.
|
||||
targetRow: null,
|
||||
// The horizontal starting position of the mouse.
|
||||
mouseStart: 0,
|
||||
// The position of the dragging animation.
|
||||
draggingTop: 0,
|
||||
// The position of the target indicator where the field is going to be moved to.
|
||||
targetTop: 0,
|
||||
// The mouse move event.
|
||||
lastMoveEvent: null,
|
||||
// Indicates if the user is auto scrolling at the moment.
|
||||
autoScrolling: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
width() {
|
||||
const allFields = [this.primary].concat(this.fields)
|
||||
return (
|
||||
allFields.reduce(
|
||||
(value, field) => this.getFieldWidth(field.id) + value,
|
||||
0
|
||||
) + this.gridViewRowDetailsWidth
|
||||
)
|
||||
},
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$options.computed = {
|
||||
...(this.$options.computed || {}),
|
||||
...mapGetters({
|
||||
rowHeight:
|
||||
this.$options.propsData.storePrefix + 'view/grid/getRowHeight',
|
||||
bufferStartIndex:
|
||||
this.$options.propsData.storePrefix + 'view/grid/getBufferStartIndex',
|
||||
rowsCount: this.$options.propsData.storePrefix + 'view/grid/getCount',
|
||||
allRows: this.$options.propsData.storePrefix + 'view/grid/getAllRows',
|
||||
}),
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.cancel()
|
||||
},
|
||||
methods: {
|
||||
getRowTop(rowId) {
|
||||
const index = this.allRows.findIndex((row) => row.id === rowId)
|
||||
if (index < 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return this.bufferStartIndex * this.rowHeight + index * this.rowHeight
|
||||
},
|
||||
/**
|
||||
* Called when the row dragging must start. It will register the global mouse
|
||||
* move, mouse up events and keyup events so that the user can drag the field to
|
||||
* the correct position.
|
||||
*/
|
||||
start(row, event) {
|
||||
const element = this.$parent[this.vertical]()
|
||||
|
||||
this.row = row
|
||||
this.startRowTop = this.getRowTop(row.id) - element.scrollTop
|
||||
this.targetRowId = row.id
|
||||
this.dragging = true
|
||||
this.mouseStart = event.clientY
|
||||
this.draggingTop = 0
|
||||
this.targetTop = 0
|
||||
|
||||
this.$el.moveEvent = (event) => this.move(event)
|
||||
window.addEventListener('mousemove', this.$el.moveEvent)
|
||||
|
||||
this.$el.upEvent = (event) => this.up(event)
|
||||
window.addEventListener('mouseup', this.$el.upEvent)
|
||||
|
||||
this.$el.keydownEvent = (event) => {
|
||||
if (event.keyCode === 27) {
|
||||
// When the user presses the escape key we want to cancel the action
|
||||
this.cancel(event)
|
||||
}
|
||||
}
|
||||
document.body.addEventListener('keydown', this.$el.keydownEvent)
|
||||
this.move(event, false)
|
||||
},
|
||||
/**
|
||||
* The move method is called when every time the user moves the mouse while
|
||||
* dragging a row. It can also be called while auto scrolling.
|
||||
*/
|
||||
move(event = null, startAutoScroll = true) {
|
||||
if (event !== null) {
|
||||
event.preventDefault()
|
||||
this.lastMoveEvent = event
|
||||
} else {
|
||||
event = this.lastMoveEvent
|
||||
}
|
||||
|
||||
// This is the vertically scrollable element.
|
||||
const element = this.$parent[this.vertical]()
|
||||
const elementRect = element.getBoundingClientRect()
|
||||
const elementHeight = elementRect.bottom - elementRect.top
|
||||
|
||||
// Calculate the top position of the dragging effect. Note that this effect lays
|
||||
// over the vertically scrollable rows.
|
||||
this.draggingTop = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
this.startRowTop + event.clientY - this.mouseStart,
|
||||
elementHeight - this.rowHeight
|
||||
)
|
||||
)
|
||||
|
||||
// Calculate before which row we want to place the row that is currently being
|
||||
// dragged. We also calculate target top position which indicates at which
|
||||
// position the row is going to be placed. Note that the target effect lays over
|
||||
// the vertically scrollable rows.
|
||||
const mouseTop = event.clientY - elementRect.top + element.scrollTop
|
||||
const rowIndex = Math.max(
|
||||
0,
|
||||
Math.min(Math.round(mouseTop / this.rowHeight), this.rowsCount)
|
||||
)
|
||||
this.targetTop = rowIndex * this.rowHeight - element.scrollTop
|
||||
const beforeRow = this.allRows[rowIndex - this.bufferStartIndex]
|
||||
this.targetRow = beforeRow === undefined ? null : beforeRow
|
||||
|
||||
// If the user is not already auto scrolling, which happens while dragging and
|
||||
// moving the element close to the end of the view port at the top or bottom
|
||||
// side, we might need to initiate that process.
|
||||
if (!this.autoScrolling || !startAutoScroll) {
|
||||
const side = Math.ceil((elementHeight / 100) * 10)
|
||||
const autoScrollMouseTop = event.clientY - elementRect.top
|
||||
const autoScrollMouseBottom = elementHeight - autoScrollMouseTop
|
||||
let speed = 0
|
||||
|
||||
if (autoScrollMouseTop < side) {
|
||||
speed = -(6 - Math.ceil((Math.max(0, autoScrollMouseTop) / side) * 6))
|
||||
} else if (autoScrollMouseBottom < side) {
|
||||
speed = 6 - Math.ceil((Math.max(0, autoScrollMouseBottom) / side) * 6)
|
||||
}
|
||||
|
||||
// If the speed is either a position or negative, so not 0, we know that we
|
||||
// need to start auto scrolling.
|
||||
if (speed !== 0) {
|
||||
this.autoScrolling = true
|
||||
this.$emit('scroll', { pixelY: speed, pixelX: 0 })
|
||||
this.$el.scrollTimeout = setTimeout(() => {
|
||||
this.move(null, false)
|
||||
}, 1)
|
||||
} else {
|
||||
this.autoScrolling = false
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Can be called when the current dragging state needs to be stopped. It will
|
||||
* remove all the created event listeners and timeouts.
|
||||
*/
|
||||
cancel() {
|
||||
this.dragging = false
|
||||
window.removeEventListener('mousemove', this.$el.moveEvent)
|
||||
window.removeEventListener('mouseup', this.$el.upEvent)
|
||||
document.body.addEventListener('keydown', this.$el.keydownEvent)
|
||||
clearTimeout(this.$el.scrollTimeout)
|
||||
},
|
||||
/**
|
||||
* Called when the user releases the mouse on a the desired position. It will
|
||||
* calculate the new position of the row in the list and if it has changed
|
||||
* position, then the order in the row options is updated accordingly.
|
||||
*/
|
||||
async up(event) {
|
||||
event.preventDefault()
|
||||
this.cancel()
|
||||
|
||||
// We don't need to do anything if the row must be placed before or after itself
|
||||
// because that wouldn't change the position.
|
||||
if (this.targetRow !== null) {
|
||||
// If the row must be placed before itself.
|
||||
if (this.row.id === this.targetRow.id) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the row needs to be placed after itself.
|
||||
const allRows = this.$store.getters[
|
||||
this.storePrefix + 'view/grid/getAllRows'
|
||||
]
|
||||
const index = allRows.findIndex((r) => r.id === this.targetRow.id)
|
||||
const after = allRows[index - 1]
|
||||
if (after && this.row.id === after.id) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const element = this.$parent[this.vertical]()
|
||||
const getScrollTop = () => element.scrollTop
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(this.storePrefix + 'view/grid/moveRow', {
|
||||
table: this.table,
|
||||
grid: this.view,
|
||||
fields: this.fields,
|
||||
primary: this.primary,
|
||||
getScrollTop,
|
||||
row: this.row,
|
||||
before: this.targetRow,
|
||||
})
|
||||
} catch (error) {
|
||||
notifyIf(error, 'row')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue