Browse Source

Merge pull request #6810 from nocodb/nc-feat/column-drag-drop

Column drag drop in grid view
pull/6812/head
Muhammed Mustafa 1 year ago committed by GitHub
parent
commit
f503bc5b35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      packages/nc-gui/components/smartsheet/grid/Table.vue
  2. 111
      packages/nc-gui/components/smartsheet/grid/useColumnDrag.ts
  3. 7
      tests/playwright/pages/Dashboard/Grid/Column/index.ts

40
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -3,6 +3,8 @@ import axios from 'axios'
import { nextTick } from '@vue/runtime-core' import { nextTick } from '@vue/runtime-core'
import type { ColumnReqType, ColumnType, PaginatedType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnReqType, ColumnType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' import { UITypes, ViewTypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { useColumnDrag } from './useColumnDrag'
import { import {
ActiveViewInj, ActiveViewInj,
CellUrlDisableOverlayInj, CellUrlDisableOverlayInj,
@ -179,6 +181,12 @@ const { isUIAllowed } = useRoles()
const hasEditPermission = computed(() => isUIAllowed('dataEdit')) const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value) const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value)
const { onDrag, onDragStart, draggedCol, dragColPlaceholderDomRef, toBeDroppedColId } = useColumnDrag({
fields,
tableBodyEl,
gridWrapper,
})
// #Variables // #Variables
const addColumnDropdown = ref(false) const addColumnDropdown = ref(false)
@ -1200,6 +1208,24 @@ const loaderText = computed(() => {
<template> <template>
<div class="flex flex-col" :class="`${headerOnly !== true ? 'h-full w-full' : ''}`"> <div class="flex flex-col" :class="`${headerOnly !== true ? 'h-full w-full' : ''}`">
<div data-testid="drag-icon-placeholder" class="absolute w-1 h-1 pointer-events-none opacity-0"></div>
<div
ref="dragColPlaceholderDomRef"
:class="{
'hidden w-0 !h-0 left-0 !max-h-0 !max-w-0': !draggedCol,
}"
class="absolute flex items-center z-40 top-0 h-full bg-gray-50 pointer-events-none opacity-60"
>
<div
v-if="draggedCol"
:style="{
'min-width': gridViewCols[draggedCol.id!]?.width || '200px',
'max-width': gridViewCols[draggedCol.id!]?.width || '200px',
'width': gridViewCols[draggedCol.id!]?.width || '200px',
}"
class="border-r-1 border-l-1 border-gray-200 h-full"
></div>
</div>
<div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative" :class="gridWrapperClass"> <div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative" :class="gridWrapperClass">
<div <div
v-show="showSkeleton && !isPaginationLoading && showLoaderAfterDelay" v-show="showSkeleton && !isPaginationLoading && showLoaderAfterDelay"
@ -1218,7 +1244,7 @@ const loaderText = computed(() => {
<div class="table-overlay" :class="{ 'nc-grid-skelton-loader': showSkeleton }"> <div class="table-overlay" :class="{ 'nc-grid-skelton-loader': showSkeleton }">
<table <table
ref="smartTable" ref="smartTable"
class="xc-row-table nc-grid backgroundColorDefault !h-auto bg-white relative" class="xc-row-table nc-grid backgroundColorDefault !h-auto bg-white relative pr-60 pb-12"
:class="{ :class="{
mobile: isMobileMode, mobile: isMobileMode,
desktop: !isMobileMode, desktop: !isMobileMode,
@ -1272,12 +1298,21 @@ const loaderText = computed(() => {
'max-width': gridViewCols[col.id]?.width || '200px', 'max-width': gridViewCols[col.id]?.width || '200px',
'width': gridViewCols[col.id]?.width || '200px', 'width': gridViewCols[col.id]?.width || '200px',
}" }"
class="nc-grid-column-header"
:class="{
'!border-r-blue-400 !border-r-3': toBeDroppedColId === col.id,
}"
@xcstartresizing="onXcStartResizing(col.id, $event)" @xcstartresizing="onXcStartResizing(col.id, $event)"
@xcresize="onresize(col.id, $event)" @xcresize="onresize(col.id, $event)"
@xcresizing="onXcResizing(col.id, $event)" @xcresizing="onXcResizing(col.id, $event)"
@click="selectColumn(col.id!)" @click="selectColumn(col.id!)"
> >
<div class="w-full h-full flex items-center"> <div
class="w-full h-full flex items-center"
:draggable="isMobileMode ? 'false' : 'true'"
@dragstart.stop="onDragStart(col.id!, $event)"
@drag.stop="onDrag($event)"
>
<LazySmartsheetHeaderVirtualCell <LazySmartsheetHeaderVirtualCell
v-if="isVirtualCol(col)" v-if="isVirtualCol(col)"
:column="col" :column="col"
@ -1501,6 +1536,7 @@ const loaderText = computed(() => {
(isLookup(columnObj) || isRollup(columnObj) || isFormula(columnObj)) && (isLookup(columnObj) || isRollup(columnObj) || isFormula(columnObj)) &&
hasEditPermission && hasEditPermission &&
isCellSelected(rowIndex, colIndex), isCellSelected(rowIndex, colIndex),
'!border-r-blue-400 !border-r-3': toBeDroppedColId === columnObj.id,
}" }"
:style="{ :style="{
'min-width': gridViewCols[columnObj.id]?.width || '200px', 'min-width': gridViewCols[columnObj.id]?.width || '200px',

111
packages/nc-gui/components/smartsheet/grid/useColumnDrag.ts

@ -0,0 +1,111 @@
import type { ColumnType } from 'nocodb-sdk'
export const useColumnDrag = ({
fields,
tableBodyEl,
gridWrapper,
}: {
fields: Ref<ColumnType[]>
tableBodyEl: Ref<HTMLElement | undefined>
gridWrapper: Ref<HTMLElement | undefined>
}) => {
const { eventBus } = useSmartsheetStoreOrThrow()
const { updateGridViewColumn, gridViewCols } = useViewColumnsOrThrow()
const { leftSidebarWidth } = storeToRefs(useSidebarStore())
const { width } = useWindowSize()
const draggedCol = ref<ColumnType | null>(null)
const dragColPlaceholderDomRef = ref<HTMLElement | null>(null)
const toBeDroppedColId = ref<string | null>(null)
const reorderColumn = async (colId: string, toColId: string) => {
const col = gridViewCols.value[colId]
const toCol = gridViewCols.value[toColId]!
const toColIndex = fields.value.findIndex((f) => f.id === toColId)
const nextToColField = toColIndex < fields.value.length - 1 ? fields.value[toColIndex + 1] : null
const nextToCol = nextToColField ? gridViewCols.value[nextToColField.id!] : null
const newOrder = nextToCol ? toCol.order! + (nextToCol.order! - toCol.order!) / 2 : toCol.order! + 1
col.order = newOrder
await updateGridViewColumn(colId, { order: newOrder } as any)
eventBus.emit(SmartsheetStoreEvents.FIELD_RELOAD)
}
const onDragStart = (colId: string, e: DragEvent) => {
if (!e.dataTransfer) return
const dom = document.querySelector('[data-testid="drag-icon-placeholder"]')
e.dataTransfer.dropEffect = 'none'
e.dataTransfer.effectAllowed = 'none'
e.dataTransfer.setDragImage(dom!, 10, 10)
e.dataTransfer.clearData()
e.dataTransfer.setData('text/plain', colId)
draggedCol.value = fields.value.find((f) => f.id === colId) ?? null
dragColPlaceholderDomRef.value!.style.height = `${tableBodyEl.value?.getBoundingClientRect().height}px`
const x = e.clientX - leftSidebarWidth.value
if (x >= 0 && dragColPlaceholderDomRef.value) {
dragColPlaceholderDomRef.value.style.left = `${x.toString()}px`
}
}
const onDrag = (e: DragEvent) => {
e.preventDefault()
if (!e.dataTransfer) return
if (!draggedCol.value) return
if (!dragColPlaceholderDomRef.value) return
if (e.clientX === 0) {
dragColPlaceholderDomRef.value!.style.left = `0px`
dragColPlaceholderDomRef.value!.style.height = '0px'
reorderColumn(draggedCol.value!.id!, toBeDroppedColId.value!)
draggedCol.value = null
toBeDroppedColId.value = null
return
}
const y = dragColPlaceholderDomRef.value!.getBoundingClientRect().top
const domsUnderMouse = document.elementsFromPoint(e.clientX, y)
const columnDom = domsUnderMouse.find((dom) => dom.classList.contains('nc-grid-column-header'))
if (columnDom) {
toBeDroppedColId.value = columnDom?.getAttribute('data-col') ?? null
}
const x = e.clientX - leftSidebarWidth.value
if (x >= 0) {
dragColPlaceholderDomRef.value.style.left = `${x.toString()}px`
}
const remInPx = parseFloat(getComputedStyle(document.documentElement).fontSize)
if (x > width.value * 0.5) {
setTimeout(() => {
gridWrapper.value!.scrollLeft += 2.5
}, 250)
} else if (x < leftSidebarWidth.value + 10 * remInPx) {
setTimeout(() => {
gridWrapper.value!.scrollLeft -= 2.5
}, 250)
}
}
return {
onDrag,
onDragStart,
draggedCol,
dragColPlaceholderDomRef,
toBeDroppedColId,
}
}

7
tests/playwright/pages/Dashboard/Grid/Column/index.ts

@ -439,7 +439,14 @@ export class ColumnPageObject extends BasePage {
this.rootPage.locator(`[data-title="${dst}"] >> .resizer`), this.rootPage.locator(`[data-title="${dst}"] >> .resizer`),
]); ]);
await fromStack.scrollIntoViewIfNeeded();
await fromStack.hover();
await fromStack.dragTo(toStack); await fromStack.dragTo(toStack);
await this.rootPage.waitForTimeout(500);
await fromStack.click({
force: true,
});
} }
async getWidth(param: { title: string }) { async getWidth(param: { title: string }) {

Loading…
Cancel
Save