From 70fd64343199f0f91081cabd1d6c25b19d26a780 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Sun, 23 Jun 2024 13:35:45 +0100 Subject: [PATCH] Simplify and improve bulk pasting logic --- .../src/components/grid/cells/DataCell.svelte | 4 - .../src/components/grid/stores/clipboard.js | 102 ++++++++++++------ .../src/components/grid/stores/ui.js | 14 ++- 3 files changed, 76 insertions(+), 44 deletions(-) diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index 3e9d6b6b05..6619bbb083 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -95,9 +95,6 @@ selectedCells.actions.stopSelecting() return } - if ($focusedCellId) { - focusedCellId.set(null) - } selectedCells.actions.updateTarget(cellId) } @@ -109,7 +106,6 @@ if (e.shiftKey && $focusedCellId) { // If we have a focused cell, select the range from that cell to here selectedCells.actions.setRange($focusedCellId, cellId) - focusedCellId.set(null) } else if (e.shiftKey && $selectedCellCount) { // If we already have a selected range of cell, update it selectedCells.actions.updateTarget(cellId) diff --git a/packages/frontend-core/src/components/grid/stores/clipboard.js b/packages/frontend-core/src/components/grid/stores/clipboard.js index 638e9414ea..ce0df1ed59 100644 --- a/packages/frontend-core/src/components/grid/stores/clipboard.js +++ b/packages/frontend-core/src/components/grid/stores/clipboard.js @@ -1,6 +1,6 @@ import { derived, writable, get } from "svelte/store" import { Helpers } from "@budibase/bbui" -import { parseCellID } from "../lib/utils" +import { parseCellID, getCellID } from "../lib/utils" export const createStores = () => { const clipboard = writable({ @@ -62,6 +62,9 @@ export const createActions = context => { rowLookupMap, rowChangeCache, rows, + focusedCellId, + columnLookupMap, + allVisibleColumns, } = context // Copies the currently selected value (or values) @@ -125,57 +128,86 @@ export const createActions = context => { return } const { value, multiCellCopy } = get(clipboard) - const $focusedCellAPI = get(focusedCellAPI) - const $selectedCells = get(selectedCells) - const $selectedCellCount = get(selectedCellCount) - const multiCellPaste = $selectedCellCount > 1 + const multiCellPaste = get(selectedCellCount) > 1 // Choose paste strategy if (multiCellCopy) { if (multiCellPaste) { // Multi to multi (only paste selected cells) - // Find the extent at which we can paste - const rowExtent = Math.min(value.length, $selectedCells.length) - const colExtent = Math.min(value[0].length, $selectedCells[0].length) - - // Build change map - let changeMap = {} - for (let rowIdx = 0; rowIdx < rowExtent; rowIdx++) { - for (let colIdx = 0; colIdx < colExtent; colIdx++) { - const cellId = $selectedCells[rowIdx][colIdx] - const { id, field } = parseCellID(cellId) - if (!changeMap[id]) { - changeMap[id] = {} - } - changeMap[id][field] = value[rowIdx][colIdx] - } - } - await rows.actions.bulkUpdate(changeMap) + await pasteIntoSelectedCells(value) } else { // Multi to single (expand to paste all values) - // TODO + // Get indices of focused cell + const $focusedCellId = get(focusedCellId) + const { id, field } = parseCellID($focusedCellId) + const $rowLookupMap = get(rowLookupMap) + const $columnLookupMap = get(columnLookupMap) + const rowIdx = $rowLookupMap[id] + const colIdx = $columnLookupMap[field] + + // Get limits of how many rows and columns we're able to paste into + const $rows = get(rows) + const $allVisibleColumns = get(allVisibleColumns) + const colCount = $allVisibleColumns.length + const rowCount = $rows.length + const selectedRows = value.length + const selectedColumns = value[0].length + const rowExtent = Math.min(selectedRows, rowCount - rowIdx) - 1 + const colExtent = Math.min(selectedColumns, colCount - colIdx) - 1 + + // Get the target cell ID (bottom right of our pastable extent) + const targetRowId = $rows[rowIdx + rowExtent]._id + const targetColName = $allVisibleColumns[colIdx + colExtent].name + const targetCellId = getCellID(targetRowId, targetColName) + + // Paste into target cell range + if (targetCellId === $focusedCellId) { + // Single cell edge case + get(focusedCellAPI).setValue(value[0][0]) + } else { + // Select the new cells to paste into, then paste + selectedCells.actions.updateTarget(targetCellId) + await pasteIntoSelectedCells(value) + } } } else { if (multiCellPaste) { // Single to multi (duplicate value in all selected cells) - let changeMap = {} - for (let row of $selectedCells) { - for (let cellId of row) { - const { id, field } = parseCellID(cellId) - if (!changeMap[id]) { - changeMap[id] = {} - } - changeMap[id][field] = value - } - } - await rows.actions.bulkUpdate(changeMap) + const $selectedCells = get(selectedCells) + const pastableValue = $selectedCells.map(row => { + return row.map(() => value) + }) + await pasteIntoSelectedCells(pastableValue) } else { // Single to single - $focusedCellAPI.setValue(value) + get(focusedCellAPI).setValue(value) } } } + // Paste the specified value into the currently selected cells + const pasteIntoSelectedCells = async value => { + const $selectedCells = get(selectedCells) + + // Find the extent at which we can paste + const rowExtent = Math.min(value.length, $selectedCells.length) + const colExtent = Math.min(value[0].length, $selectedCells[0].length) + + // Build change map + let changeMap = {} + for (let rowIdx = 0; rowIdx < rowExtent; rowIdx++) { + for (let colIdx = 0; colIdx < colExtent; colIdx++) { + const cellId = $selectedCells[rowIdx][colIdx] + const { id, field } = parseCellID(cellId) + if (!changeMap[id]) { + changeMap[id] = {} + } + changeMap[id][field] = value[rowIdx][colIdx] + } + } + await rows.actions.bulkUpdate(changeMap) + } + return { clipboard: { ...clipboard, diff --git a/packages/frontend-core/src/components/grid/stores/ui.js b/packages/frontend-core/src/components/grid/stores/ui.js index bd3e973a5c..10a17a372a 100644 --- a/packages/frontend-core/src/components/grid/stores/ui.js +++ b/packages/frontend-core/src/components/grid/stores/ui.js @@ -217,9 +217,8 @@ export const createActions = context => { toggleSelectedRow(id) return } - // There should always be a last selected index if (lastSelectedIndex == null) { - throw "NO LAST SELECTED INDEX" + return } const thisIndex = get(rowLookupMap)[id] @@ -417,10 +416,15 @@ export const initialise = context => { } }) - // Clear selected rows when selecting cells + // Clear state when selecting cells selectedCellCount.subscribe($selectedCellCount => { - if ($selectedCellCount && get(selectedRowCount)) { - selectedRows.set({}) + if ($selectedCellCount) { + if (get(selectedRowCount)) { + selectedRows.set({}) + } + if (get(focusedCellId)) { + focusedCellId.set(null) + } } }) }