Add multi to multi pasting

This commit is contained in:
Andrew Kingston 2024-06-21 20:38:48 +01:00
parent ad0d300ff9
commit 9657781df6
No known key found for this signature in database
2 changed files with 107 additions and 26 deletions

View File

@ -5,9 +5,8 @@ import { parseCellID } from "../lib/utils"
export const createStores = () => { export const createStores = () => {
const clipboard = writable({ const clipboard = writable({
value: null, value: null,
multiCellMode: false, multiCellCopy: false,
}) })
return { return {
clipboard, clipboard,
} }
@ -16,10 +15,12 @@ export const createStores = () => {
export const deriveStores = context => { export const deriveStores = context => {
const { clipboard, focusedCellAPI, selectedCellCount } = context const { clipboard, focusedCellAPI, selectedCellCount } = context
// Derive whether or not we're able to copy
const copyAllowed = derived(focusedCellAPI, $focusedCellAPI => { const copyAllowed = derived(focusedCellAPI, $focusedCellAPI => {
return $focusedCellAPI != null return $focusedCellAPI != null
}) })
// Derive whether or not we're able to paste
const pasteAllowed = derived( const pasteAllowed = derived(
[clipboard, focusedCellAPI, selectedCellCount], [clipboard, focusedCellAPI, selectedCellCount],
([$clipboard, $focusedCellAPI, $selectedCellCount]) => { ([$clipboard, $focusedCellAPI, $selectedCellCount]) => {
@ -30,7 +31,7 @@ export const deriveStores = context => {
// this cell is readonly // this cell is readonly
const multiCellPaste = $selectedCellCount > 1 const multiCellPaste = $selectedCellCount > 1
if ( if (
!$clipboard.multiCellMode && !$clipboard.multiCellCopy &&
!multiCellPaste && !multiCellPaste &&
$focusedCellAPI.isReadonly() $focusedCellAPI.isReadonly()
) { ) {
@ -49,44 +50,93 @@ export const deriveStores = context => {
export const createActions = context => { export const createActions = context => {
const { const {
clipboard, clipboard,
selectedCellCount,
focusedCellAPI, focusedCellAPI,
copyAllowed, copyAllowed,
pasteAllowed, pasteAllowed,
rows,
selectedCells, selectedCells,
rowLookupMap,
rowChangeCache,
rows,
columnLookupMap,
} = context } = context
// Copies the currently selected value (or values)
const copy = () => { const copy = () => {
if (!get(copyAllowed)) { if (!get(copyAllowed)) {
return return
} }
const $selectedCellCount = get(selectedCellCount) const cellIds = Object.keys(get(selectedCells))
const $focusedCellAPI = get(focusedCellAPI) const $focusedCellAPI = get(focusedCellAPI)
const multiCellMode = $selectedCellCount > 1 const multiCellCopy = cellIds.length > 1
// Multiple values to copy // Multiple values to copy
if (multiCellMode) { if (multiCellCopy) {
// TODO const $rowLookupMap = get(rowLookupMap)
return const $rowChangeCache = get(rowChangeCache)
} const $rows = get(rows)
const $columnLookupMap = get(columnLookupMap)
// Single value to copy // Go through each selected cell and group all selected cell values by
const value = $focusedCellAPI.getValue() // their row ID. Order is important for pasting, so we store the index of
clipboard.set({ // both rows and values.
value, let map = {}
multiCellMode, for (let cellId of cellIds) {
}) const { id, field } = parseCellID(cellId)
const index = $rowLookupMap[id]
if (!map[id]) {
map[id] = {
order: index,
values: [],
}
}
const row = {
...$rows[index],
...$rowChangeCache[id],
}
const columnIndex = $columnLookupMap[field]
map[id].values.push({
value: row[field],
order: columnIndex,
})
}
// Also copy a stringified version to the clipboard // Sort rows by order
let stringified = "" let value = []
if (value != null && value !== "") { const sortedRowValues = Object.values(map)
// Only conditionally stringify to avoid redundant quotes around text .toSorted((a, b) => a.order - b.order)
stringified = typeof value === "object" ? JSON.stringify(value) : value .map(x => x.values)
// Sort all values in each row by order
for (let rowValues of sortedRowValues) {
value.push(
rowValues.toSorted((a, b) => a.order - b.order).map(x => x.value)
)
}
// Update state
clipboard.set({
value,
multiCellCopy: true,
})
} else {
// Single value to copy
const value = $focusedCellAPI.getValue()
clipboard.set({
value,
multiCellCopy,
})
// Also copy a stringified version to the clipboard
let stringified = ""
if (value != null && value !== "") {
// Only conditionally stringify to avoid redundant quotes around text
stringified = typeof value === "object" ? JSON.stringify(value) : value
}
Helpers.copyToClipboard(stringified)
} }
Helpers.copyToClipboard(stringified)
} }
// Pastes the previously copied value(s) into the selected cell(s)
const paste = async () => { const paste = async () => {
if (!get(pasteAllowed)) { if (!get(pasteAllowed)) {
return return
@ -98,14 +148,45 @@ export const createActions = context => {
} }
// Check if we're pasting into one or more cells // Check if we're pasting into one or more cells
const cellIds = Object.keys(get(selectedCells)) const $selectedCells = get(selectedCells)
const cellIds = Object.keys($selectedCells)
const multiCellPaste = cellIds.length > 1 const multiCellPaste = cellIds.length > 1
if ($clipboard.multiCellMode) { if ($clipboard.multiCellCopy) {
if (multiCellPaste) { if (multiCellPaste) {
// Multi to multi (only paste selected cells) // Multi to multi (only paste selected cells)
const value = $clipboard.value
// Find the top left index so we can find the relative offset for each
// cell
let rowIndices = []
let columnIndices = []
for (let cellId of cellIds) {
rowIndices.push($selectedCells[cellId].rowIdx)
columnIndices.push($selectedCells[cellId].colIdx)
}
const minRowIdx = Math.min(...rowIndices)
const minColIdx = Math.min(...columnIndices)
// Build change map of values to patch
let changeMap = {}
const $rowLookupMap = get(rowLookupMap)
const $columnLookupMap = get(columnLookupMap)
for (let cellId of cellIds) {
const { id, field } = parseCellID(cellId)
const rowIdx = $rowLookupMap[id] - minRowIdx
const colIdx = $columnLookupMap[field] - minColIdx
if (colIdx in (value[rowIdx] || [])) {
if (!changeMap[id]) {
changeMap[id] = {}
}
changeMap[id][field] = value[rowIdx][colIdx]
}
}
await rows.actions.bulkUpdate(changeMap)
} else { } else {
// Multi to single (expand to paste all values) // Multi to single (expand to paste all values)
// TODO
} }
} else { } else {
if (multiCellPaste) { if (multiCellPaste) {

View File

@ -60,7 +60,7 @@ export const deriveStores = context => {
rowId = $rows[rowIdx]._id rowId = $rows[rowIdx]._id
colName = $allVisibleColumns[colIdx].name colName = $allVisibleColumns[colIdx].name
cellId = getCellID(rowId, colName) cellId = getCellID(rowId, colName)
map[cellId] = true map[cellId] = { rowIdx, colIdx }
} }
} }
return map return map