1
0
mirror of https://gitlab.com/bramw/baserow.git synced 2024-11-21 23:37:55 +00:00
bramw_baserow/web-frontend/modules/database/mixins/bufferedRowsDragAndDrop.js

232 lines
7.9 KiB
JavaScript

import { mapGetters } from 'vuex'
import { notifyIf } from '@baserow/modules/core/utils/error'
/**
* This mixin can be used in combination with a view component that uses the
* bufferedRows store mixin. It allows for drag and drop ordering of the items. It
* can be used by adding the following listeners to the row objects.
*
* @mousedown="rowDown($event, row)"
* @mousemove="rowMoveOver($event, row)"
* @mouseenter="rowMoveOver($event, row)"
*
* Note that the parent component must implement the `getDragAndDropStoreName` method.
*/
export default {
data() {
return {
// Must be overwritten by the parent component. It should hold the class name
// of the clone of the dom element when the user starts dragging the row.
dragAndDropCloneClass: '',
// Indicates whether a transition effect is currently active.
dragAndDropTransitioning: false,
// The row object that the user clicked on.
dragAndDropDownRow: null,
// The initial horizontal position absolute client position of the card after
// mousedown.
dragAndDropRowClientX: 0,
// The initial vertical position absolute client position of the card after
// mousedown.
dragAndDropRowClientY: 0,
}
},
beforeCreate() {
this.$options.computed = {
...(this.$options.computed || {}),
...mapGetters({
dragAndDropDraggingRow: `${this.$options.methods.getDragAndDropStoreName(
this.$options.propsData
)}/getDraggingRow`,
}),
}
},
methods: {
/**
* Should return the name of the store that's has the bufferedRows mixed in.
* This can be different for each component, to this method must be overwritten.
*/
getDragAndDropStoreName(props) {
throw new Error(
'The `getDragAndDropStoreName` method must be implemented.'
)
},
/**
* Called when a user presses the left mouse on a card. This method will prepare
* the dragging if the user moves the mouse a bit. Otherwise, if the mouse is
* release without moving, the edit modal is opened.
*/
rowDown(event, row, readOnly = false) {
// If it isn't a left click.
if (event.button !== 0 || row === null) {
return
}
event.preventDefault()
this.dragAndDropDownRow = row
this.$el.mouseUpEvent = (event) => this.rowUp(event)
window.addEventListener('mouseup', this.$el.mouseUpEvent)
if (!readOnly) {
const rect = event.target.getBoundingClientRect()
this.dragAndDropRowClientX = event.clientX
this.dragAndDropRowClientY = event.clientY
this.dragAndDropDownRowTop = event.clientY - rect.top
this.dragAndDropDownRowLeft = event.clientX - rect.left
this.clonedElement = document.createElement('div')
this.clonedElement.innerHTML = event.target.outerHTML
this.clonedElement.style = `position: absolute; left: 0; top: 0; width: ${rect.width}px; z-index: 10;`
this.clonedElement.firstChild.classList.add(this.dragAndDropCloneClass)
this.clonedWrapper = document.createElement('div')
this.clonedWrapper.style =
'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; pointer-event: none;'
this.clonedWrapper.appendChild(this.clonedElement)
this.$el.keydownEvent = (event) => {
if (event.key === 'Escape') {
if (this.dragAndDropDraggingRow !== null) {
this.$store.dispatch(
`${this.getDragAndDropStoreName(this)}/cancelRowDrag`,
{
view: this.view,
fields: this.fields,
row: this.dragAndDropDraggingRow,
}
)
}
this.rowCancel(event)
}
}
document.body.addEventListener('keydown', this.$el.keydownEvent)
this.$el.mouseMoveEvent = (event) => this.rowMove(event)
window.addEventListener('mousemove', this.$el.mouseMoveEvent)
this.rowMove(event)
}
},
/**
* Called when moving the mouse after the down event on a row. If we're not in a
* dragging state already, it must be started if the user moves more than 3 pixels.
*/
async rowMove(event) {
if (
this.dragAndDropDraggingRow === null &&
// We only want to allow ordering by drag and drop if there aren't any
// sortings because if there are, the ordering is not only based of the
// `order` property and drag and dropping in the right position doesn't make
// sense.
this.view.sortings.length === 0
) {
if (
Math.abs(event.clientX - this.dragAndDropRowClientX) > 3 ||
Math.abs(event.clientY - this.dragAndDropRowClientY) > 3
) {
document.body.appendChild(this.clonedWrapper)
await this.$store.dispatch(
`${this.getDragAndDropStoreName(this)}/startRowDrag`,
{
row: this.dragAndDropDownRow,
}
)
}
}
this.clonedElement.style.top =
event.clientY - this.dragAndDropDownRowTop + 'px'
this.clonedElement.style.left =
event.clientX - this.dragAndDropDownRowLeft + 'px'
},
/**
* Called when the user release the mouse after pressing the left mouse button
* on the row. It will check if we're in a dragging state and if so, we need to
* stop the dragging of the row and make the new position persistent.
*/
async rowUp() {
if (this.dragAndDropDraggingRow !== null) {
this.rowCancel()
try {
await this.$store.dispatch(
`${this.getDragAndDropStoreName(this)}/stopRowDrag`,
{
table: this.table,
view: this.view,
fields: this.fields,
primary: this.primary,
}
)
} catch (error) {
notifyIf(error)
}
} else {
// Call the `rowClick` method because in some cases, we might want to take
// certain action after a "normal" click on the row. Like for example
// opening a row edit modal.
this.rowClick(this.dragAndDropDownRow)
this.rowCancel()
}
},
rowCancel() {
this.dragAndDropDownRow = null
// If the view is read only, the clonedWrapper is never created.
if (this.clonedWrapper) {
this.clonedWrapper.remove()
}
document.body.removeEventListener('keydown', this.$el.keydownEvent)
window.removeEventListener('mousemove', this.$el.mouseMoveEvent)
window.removeEventListener('mouseup', this.$el.mouseUpEvent)
},
/**
* Must be called when the user hovers over another row. It will check if we're
* currently in a dragging state and if so, the row is temporarily moved to the
* new position.
*/
async rowMoveOver(event, row) {
if (
row === null ||
this.dragAndDropDraggingRow === null ||
this.dragAndDropDraggingRow.id === row.id ||
this.dragAndDropTransitioning
) {
return
}
const moved = await this.$store.dispatch(
`${this.getDragAndDropStoreName(this)}/forceMoveRowBefore`,
{
row: this.dragAndDropDraggingRow,
targetRow: row,
}
)
if (moved) {
this.rowMoved()
}
},
/**
* After a row has been moved, we need to temporarily need to set the transition
* state to true. While it's true, it can't be moved to another position to avoid
* strange transition effects of other cards.
*/
rowMoved() {
this.dragAndDropTransitioning = true
setTimeout(
() => {
this.dragAndDropTransitioning = false
},
// Must be kept in sync with the transition-duration of
// gallery.scss.gallery-view__cards--dragging
100 + 1
)
},
/**
* Is called when the user clicks on a row without moving it to another position.
*/
rowClick() {},
},
}