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