mirror of
https://gitlab.com/bramw/baserow.git
synced 2024-11-21 23:37:55 +00:00
232 lines
7.9 KiB
JavaScript
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() {},
|
|
},
|
|
}
|