diff --git a/packages/nc-gui/components/dlg/ExpandTable.vue b/packages/nc-gui/components/dlg/ExpandTable.vue
index 2d37dedd76..9fe7a43d5b 100644
--- a/packages/nc-gui/components/dlg/ExpandTable.vue
+++ b/packages/nc-gui/components/dlg/ExpandTable.vue
@@ -5,6 +5,7 @@ const props = defineProps<{
fields?: number
rows?: number
modelValue: boolean
+ affectedRows?: number
}>()
const emit = defineEmits(['update:expand', 'cancel', 'update:modelValue'])
@@ -40,13 +41,16 @@ onKeyDown('esc', () => {
-
- To fit your pasted data into the table, we need to add
+
+ The data you pasted will update {{ affectedRows }} records in subsequent pages.
+
+
+ To fit your pasted data into the table, we need to add
{{ rows }} more records.
-
+
-.ant-form-item {
- @apply mb-0;
-}
-
-.nc-input-text-area {
- padding-block: 8px !important;
-}
-
-.nc-table-advanced-options {
- max-height: 0;
- transition: 0.3s max-height;
- overflow: hidden;
-
- &.active {
- max-height: 100px;
- }
-}
-
+
diff --git a/packages/nc-gui/components/smartsheet/grid/Table.vue b/packages/nc-gui/components/smartsheet/grid/Table.vue
index e1f8883af7..8e88545d08 100644
--- a/packages/nc-gui/components/smartsheet/grid/Table.vue
+++ b/packages/nc-gui/components/smartsheet/grid/Table.vue
@@ -793,7 +793,7 @@ function scrollToRow(row?: number) {
const isOpen = ref(false)
-async function expandRows(rowCount: number) {
+async function expandRows(rowCount: number, rowsAffected: number) {
isOpen.value = true
const options = {
@@ -808,6 +808,7 @@ async function expandRows(rowCount: number) {
const { close } = useDialog(resolveComponent('DlgExpandTable'), {
'modelValue': isOpen,
'rows': rowCount,
+ 'affectedRows': rowsAffected,
'onUpdate:expand': closeDialog,
'onUpdate:modelValue': closeDlg,
})
diff --git a/packages/nc-gui/composables/useData.ts b/packages/nc-gui/composables/useData.ts
index 04b3a850ca..1260e6ca38 100644
--- a/packages/nc-gui/composables/useData.ts
+++ b/packages/nc-gui/composables/useData.ts
@@ -1,4 +1,5 @@
import {
+ type Api,
type ColumnType,
type LinkToAnotherRecordType,
type PaginatedType,
@@ -9,7 +10,7 @@ import {
} from 'nocodb-sdk'
import { UITypes, isCreatedOrLastModifiedByCol, isCreatedOrLastModifiedTimeCol } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
-import type { CellRange } from '#imports'
+import type { CellRange, Row } from '#imports'
export function useData(args: {
meta: Ref | ComputedRef
@@ -18,7 +19,11 @@ export function useData(args: {
paginationData: Ref
callbacks?: {
changePage?: (page: number) => Promise
- loadData?: () => Promise
+ loadData?: (
+ param?: Parameters['dbViewRow']['list']>[4],
+ shouldShowLoading?: boolean,
+ updateLocalState?: boolean,
+ ) => Promise | Promise
globalCallback?: (...args: any[]) => Promise
syncCount?: () => Promise
syncPagination?: () => Promise
@@ -161,15 +166,34 @@ export function useData(args: {
isPaginationLoading.value = true
try {
- const ogUpdateRows = formattedData.value
- .filter((row) =>
- updateRows.some(
- (r) =>
- extractPkFromRow(r.oldRow, metaValue?.columns as ColumnType[]) ===
- extractPkFromRow(row.row, metaValue?.columns as ColumnType[]),
- ),
- )
- .map(clone)
+ // Identify the rows that need to be fetched from the server
+
+ const rowsToFetch = updateRows.filter((row) => row.rowMeta?.isExistingRow)
+ const pagesToFetch = Array.from(new Set(rowsToFetch.map((row) => row.rowMeta.page))).filter(
+ (page): page is number => page !== undefined && page !== paginationData.value.page,
+ )
+ const fetchedData: Record = {}
+
+ for (const pageToFetch of pagesToFetch) {
+ fetchedData[pageToFetch] = (await callbacks?.loadData?.(
+ {
+ offset: (pageToFetch - 1) * paginationData.value.pageSize!,
+ },
+ false,
+ false,
+ )) as Row[]
+ }
+
+ const getPk = (row: Row) => extractPkFromRow(row.row, metaValue?.columns as ColumnType[])
+
+ const ogUpdateRows = updateRows.map((row) => {
+ if (row.rowMeta?.page && row.rowMeta.page !== paginationData.value.page) {
+ const fetchedRow = fetchedData[row.rowMeta.page]?.[row.rowMeta.rowInPage!]
+ return fetchedRow ? clone(fetchedRow) : row
+ }
+ const currentPageRow = formattedData.value.find((formattedRow) => getPk(formattedRow) === getPk(row))
+ return currentPageRow ? clone(currentPageRow) : row
+ })
const cleanRow = (row: any) => {
const cleanedRow = { ...row }
@@ -179,6 +203,20 @@ export function useData(args: {
return cleanedRow
}
+ updateRows = updateRows.map((row) => {
+ if (row.rowMeta?.page && row.rowMeta.page !== paginationData.value.page) {
+ const fetchedRow = fetchedData[row.rowMeta.page]?.[row.rowMeta.rowInPage!]
+ if (fetchedRow) {
+ return {
+ ...fetchedRow,
+ row: { ...fetchedRow.row, ...row.row },
+ oldRow: fetchedRow.row,
+ }
+ }
+ }
+ return row
+ })
+
const rowsToUpsert = {
insert: insertRows.map((row) => cleanRow(row.row)),
update: updateRows.map((row) => cleanRow(row.row)),
diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts
index 246927c787..13b26b74dc 100644
--- a/packages/nc-gui/composables/useMultiSelect/index.ts
+++ b/packages/nc-gui/composables/useMultiSelect/index.ts
@@ -848,98 +848,101 @@ export function useMultiSelect(
const selectionRowCount = Math.max(clipboardMatrix.length, selectedRange.end.row - selectedRange.start.row + 1)
- const pasteMatrixRows = selectionRowCount
const pasteMatrixCols = clipboardMatrix[0].length
const colsToPaste = unref(fields).slice(activeCell.col, activeCell.col + pasteMatrixCols)
- const rowsToPaste = unref(data).slice(activeCell.row, activeCell.row + selectionRowCount)
+
+ const { totalRows = 0, page = 1, pageSize = 100 } = unref(paginationDataRef)!
+ const totalRowsBeforeActiveCell = (page - 1) * pageSize + activeCell.row
+ const availableRowsToUpdate = Math.max(0, totalRows - totalRowsBeforeActiveCell)
+ const rowsToAdd = Math.max(0, selectionRowCount - availableRowsToUpdate)
+
+ const rowsInCurrentPage = unref(data).length
+ const rowsAffectedInCurrentPage = Math.min(selectionRowCount, rowsInCurrentPage - activeCell.row)
+ const rowsAffectedInOtherPages = Math.max(0, selectionRowCount - rowsAffectedInCurrentPage)
let options = {
continue: false,
- expand: true,
+ expand: rowsToAdd > 0 || rowsAffectedInOtherPages > 0 || (rowsAffectedInOtherPages > 0 && rowsToAdd > 0),
}
-
- const rowToPasteLen = rowsToPaste.length
-
- if (pasteMatrixRows > rowsToPaste.length) {
- rowsToPaste.push(
- ...Array(pasteMatrixRows - rowsToPaste.length)
- .fill({})
- .map(() => ({
- row: {},
- oldRow: {},
- rowMeta: {
- isExpandedData: true,
- },
- })),
- )
- options = await expandRows(pasteMatrixRows - rowToPasteLen)
-
- if (!options.continue) {
- return
- }
+ if (options.expand) {
+ options = await expandRows(rowsToAdd, rowsAffectedInOtherPages)
+ if (!options.continue) return
}
- const propsToPaste: string[] = []
- let pastedRows = 0
+ const updatedRows: any[] = []
+ const newRows: any[] = []
+ const propsToPaste: string[] = []
let isInfoShown = false
- for (let i = 0; i < pasteMatrixRows; i++) {
- const pasteRow = rowsToPaste[i]
-
- // TODO handle insert new row
- if (!pasteRow || pasteRow.rowMeta.new) break
-
- pastedRows++
-
- for (let j = 0; j < pasteMatrixCols; j++) {
- const pasteCol = colsToPaste[j]
- if (!isPasteable(pasteRow, pasteCol)) {
- if ((isBt(pasteCol) || isOo(pasteCol) || isMm(pasteCol)) && !isInfoShown) {
- message.info(t('msg.info.groupPasteIsNotSupportedOnLinksColumn'))
- isInfoShown = true
- }
- continue
+ for (let i = 0; i < selectionRowCount; i++) {
+ const clipboardRowIndex = i % clipboardMatrix.length
+ let targetRow: any
+
+ if (i < availableRowsToUpdate) {
+ const absoluteRowIndex = totalRowsBeforeActiveCell + i
+ targetRow =
+ i < unref(data).length
+ ? unref(data)[i]
+ : {
+ row: {},
+ oldRow: {},
+ rowMeta: {
+ isExistingRow: true,
+ page: Math.floor(absoluteRowIndex / pageSize) + 1,
+ rowInPage: absoluteRowIndex % pageSize,
+ },
+ }
+ updatedRows.push(targetRow)
+ } else {
+ targetRow = {
+ row: {},
+ oldRow: {},
+ rowMeta: {
+ isExistingRow: false,
+ },
}
+ newRows.push(targetRow)
+ }
- propsToPaste.push(pasteCol.title!)
-
- const pasteValue = convertCellData(
- {
- // Repeat the clipboard data array if the matrix is smaller than the selection
- value: clipboardMatrix[i % clipboardMatrix.length][j],
- to: pasteCol.uidt as UITypes,
- column: pasteCol,
- appInfo: unref(appInfo),
- oldValue: pasteCol.uidt === UITypes.Attachment ? pasteRow.row[pasteCol.title!] : undefined,
- },
- isMysql(meta.value?.source_id),
- true,
- )
+ for (let j = 0; j < clipboardMatrix[clipboardRowIndex].length; j++) {
+ const column = colsToPaste[j]
+ if (column && isPasteable(targetRow, column)) {
+ propsToPaste.push(column.title!)
+ const pasteValue = convertCellData(
+ {
+ value: clipboardMatrix[clipboardRowIndex][j],
+ to: column.uidt as UITypes,
+ column,
+ appInfo: unref(appInfo),
+ oldValue: column.uidt === UITypes.Attachment ? targetRow.row[column.title!] : undefined,
+ },
+ isMysql(meta.value?.source_id),
+ true,
+ )
- if (pasteValue !== undefined) {
- pasteRow.row[pasteCol.title!] = pasteValue
+ if (pasteValue !== undefined) {
+ targetRow.row[column.title!] = pasteValue
+ }
+ } else if ((isBt(column) || isOo(column) || isMm(column)) && !isInfoShown) {
+ message.info(t('msg.info.groupPasteIsNotSupportedOnLinksColumn'))
+ isInfoShown = true
}
}
}
- const expandedRows = rowsToPaste.filter((row) => row.rowMeta.isExpandedData)
-
- const updatedRows = rowsToPaste.filter((row) => !row.rowMeta.isExpandedData)
-
if (options.expand) {
- await bulkUpsertRows?.(expandedRows!, updatedRows, propsToPaste)
+ await bulkUpsertRows?.(newRows, updatedRows, propsToPaste)
} else {
await bulkUpdateRows?.(updatedRows, propsToPaste)
}
+
if (scrollToCell) {
scrollToCell(activeCell.row, activeCell.col)
}
- if (pastedRows > 0) {
- // highlight the pasted range
- selectedRange.startRange({ row: activeCell.row, col: activeCell.col })
- selectedRange.endRange({ row: activeCell.row + pastedRows - 1, col: activeCell.col + pasteMatrixCols - 1 })
- }
+
+ selectedRange.startRange({ row: activeCell.row, col: activeCell.col })
+ selectedRange.endRange({ row: activeCell.row + selectionRowCount - 1, col: activeCell.col + pasteMatrixCols - 1 })
} else {
if (selectedRange.isSingleCell()) {
const rowObj = unref(data)[activeCell.row]
diff --git a/packages/nc-gui/composables/useViewData.ts b/packages/nc-gui/composables/useViewData.ts
index ca1e851c68..27487ccefd 100644
--- a/packages/nc-gui/composables/useViewData.ts
+++ b/packages/nc-gui/composables/useViewData.ts
@@ -161,7 +161,11 @@ export function useViewData(
const controller = ref()
- async function loadData(params: Parameters['dbViewRow']['list']>[4] = {}, shouldShowLoading = true) {
+ async function loadData(
+ params: Parameters['dbViewRow']['list']>[4] = {},
+ shouldShowLoading = true,
+ updateLocalState = true,
+ ) {
if ((!base?.value?.id || !metaId.value || !viewMeta.value?.id) && !isPublic.value) return
if (controller.value) {
@@ -213,27 +217,34 @@ export function useViewData(
console.error(error)
return message.error(await extractSdkResponseErrorMsg(error))
}
- formattedData.value = formatData(response.list)
- paginationData.value = response.pageInfo || paginationData.value || {}
- // if public then update sharedPaginationData
- if (isPublic.value) {
- sharedPaginationData.value = paginationData.value
- }
+ if (updateLocalState) {
+ formattedData.value = formatData(response.list)
+ paginationData.value = response.pageInfo || paginationData.value || {}
+
+ // if public then update sharedPaginationData
+ if (isPublic.value) {
+ sharedPaginationData.value = paginationData.value
+ }
- excludePageInfo.value = !response.pageInfo
- isPaginationLoading.value = false
+ excludePageInfo.value = !response.pageInfo
+ isPaginationLoading.value = false
- // to cater the case like when querying with a non-zero offset
- // the result page may point to the target page where the actual returned data don't display on
- if (paginationData.value.totalRows !== undefined && paginationData.value.totalRows !== null) {
- const expectedPage = Math.max(1, Math.ceil(paginationData.value.totalRows! / paginationData.value.pageSize!))
- if (expectedPage < paginationData.value.page!) {
- await changePage(expectedPage)
+ // to cater the case like when querying with a non-zero offset
+ // the result page may point to the target page where the actual returned data don't display on
+ if (paginationData.value.totalRows !== undefined && paginationData.value.totalRows !== null) {
+ const expectedPage = Math.max(1, Math.ceil(paginationData.value.totalRows! / paginationData.value.pageSize!))
+ if (expectedPage < paginationData.value.page!) {
+ await changePage(expectedPage)
+ }
+ }
+ if (viewMeta.value?.type === ViewTypes.GRID) {
+ loadAggCommentsCount()
}
}
- if (viewMeta.value?.type === ViewTypes.GRID) {
- loadAggCommentsCount()
+
+ if (!updateLocalState) {
+ return formatData(response.list)
}
}
diff --git a/packages/nc-gui/lib/types.ts b/packages/nc-gui/lib/types.ts
index 9f09f12176..04a992f15a 100644
--- a/packages/nc-gui/lib/types.ts
+++ b/packages/nc-gui/lib/types.ts
@@ -84,6 +84,9 @@ interface Row {
//
isExpandedData?: boolean
+ isExistingRow?: boolean
+ page?: number
+ rowInPage?: number
// use in datetime picker component
isUpdatedFromCopyNPaste?: Record