Clean up and improve copy/paste flows

This commit is contained in:
Andrew Kingston 2024-06-21 15:17:49 +01:00
parent a86f891c04
commit 502c2541e5
No known key found for this signature in database
7 changed files with 158 additions and 42 deletions

View File

@ -10,7 +10,6 @@
focusedCellId,
stickyColumn,
columns,
menu,
selectedRowCount,
} = getContext("grid")
@ -18,7 +17,6 @@
// Deletion callback when confirmed
const performDuplication = async () => {
menu.actions.close()
const rowsToDuplicate = Object.keys($selectedRows).map(id => {
return rows.actions.getRow(id)
})
@ -27,9 +25,6 @@
const column = $stickyColumn?.name || $columns[0].name
$focusedCellId = getCellID(newRows[0]._id, column)
}
// Ensure menu is closed, as we may have triggered this from there
menu.actions.close()
}
onMount(() => subscribe("request-bulk-duplicate", () => modal?.show()))

View File

@ -0,0 +1,45 @@
<script>
import { Modal, ModalContent } from "@budibase/bbui"
import { getContext, onMount } from "svelte"
const { clipboard, subscribe, copyAllowed, pasteAllowed, selectedCellCount } =
getContext("grid")
let modal
const copy = () => {
if (!$copyAllowed) {
return
}
clipboard.actions.copy()
}
const paste = async () => {
if (!$pasteAllowed) {
return
}
// Prompt if paste will update multiple cells
const multiCellPaste = $selectedCellCount > 1
const prompt = $clipboard.multiCellCopy || multiCellPaste
if (prompt) {
modal?.show()
} else {
clipboard.actions.paste()
}
}
onMount(() => subscribe("copy", copy))
onMount(() => subscribe("paste", paste))
</script>
<Modal bind:this={modal}>
<ModalContent
title="Confirm bulk paste"
confirmText="Continue"
cancelText="Cancel"
onConfirm={clipboard.actions.paste}
size="M"
>
Are you sure you want to paste values into {$selectedCellCount} cells?
</ModalContent>
</Modal>

View File

@ -8,6 +8,7 @@
import { attachStores } from "../stores"
import BulkDeleteHandler from "../controls/BulkDeleteHandler.svelte"
import BulkDuplicationHandler from "../controls/BulkDuplicationHandler.svelte"
import ClipboardHandler from "../controls/ClipboardHandler.svelte"
import GridBody from "./GridBody.svelte"
import ResizeOverlay from "../overlays/ResizeOverlay.svelte"
import ReorderOverlay from "../overlays/ReorderOverlay.svelte"
@ -216,6 +217,7 @@
{#if $config.canAddRows}
<BulkDuplicationHandler />
{/if}
<ClipboardHandler />
<KeyboardManager />
</div>

View File

@ -11,7 +11,6 @@
focusedRow,
stickyColumn,
focusedCellAPI,
clipboard,
dispatch,
selectedRows,
config,
@ -97,12 +96,10 @@
if (e.metaKey || e.ctrlKey) {
switch (e.key) {
case "c":
clipboard.actions.copy()
dispatch("copy")
break
case "v":
if (!api?.isReadonly()) {
clipboard.actions.paste()
}
dispatch("paste")
break
case "Enter":
if ($config.canAddRows) {

View File

@ -13,14 +13,13 @@
focusedCellId,
stickyColumn,
config,
copiedCell,
clipboard,
dispatch,
focusedCellAPI,
focusedRowId,
notifications,
hasBudibaseIdentifiers,
selectedRowCount,
copyAllowed,
pasteAllowed,
} = getContext("grid")
let anchor
@ -33,16 +32,12 @@
}
const deleteRow = () => {
rows.actions.deleteRows([$focusedRow])
menu.actions.close()
rows.actions.deleteRows([$focusedRow])
$notifications.success("Deleted 1 row")
}
const bulkDelete = () => {
dispatch("request-bulk-delete")
}
const duplicate = async () => {
const duplicateRow = async () => {
menu.actions.close()
const newRow = await rows.actions.duplicateRow($focusedRow)
if (newRow) {
@ -51,10 +46,6 @@
}
}
const bulkDuplicate = () => {
dispatch("request-bulk-duplicate")
}
const copyToClipboard = async value => {
await Helpers.copyToClipboard(value)
$notifications.success("Copied to clipboard")
@ -71,29 +62,32 @@
<MenuItem
icon="Duplicate"
disabled={!$config.canAddRows || $selectedRowCount > 50}
on:click={bulkDuplicate}
on:click={() => dispatch("request-bulk-duplicate")}
on:click={menu.actions.close}
>
Duplicate {$selectedRowCount} rows
</MenuItem>
<MenuItem
icon="Delete"
disabled={!$config.canDeleteRows}
on:click={bulkDelete}
on:click={() => dispatch("request-bulk-delete")}
on:click={menu.actions.close}
>
Delete {$selectedRowCount} rows
</MenuItem>
{:else if $menu.multiCellMode}
<MenuItem
icon="Copy"
on:click={clipboard.actions.copy}
disabled={!$copyAllowed}
on:click={() => dispatch("copy")}
on:click={menu.actions.close}
>
Copy
</MenuItem>
<MenuItem
icon="Paste"
disabled={$copiedCell == null || $focusedCellAPI?.isReadonly()}
on:click={clipboard.actions.paste}
disabled={!$pasteAllowed}
on:click={() => dispatch("paste")}
on:click={menu.actions.close}
>
Paste
@ -104,15 +98,16 @@
{:else}
<MenuItem
icon="Copy"
on:click={clipboard.actions.copy}
disabled={!$copyAllowed}
on:click={() => dispatch("copy")}
on:click={menu.actions.close}
>
Copy
</MenuItem>
<MenuItem
icon="Paste"
disabled={$copiedCell == null || $focusedCellAPI?.isReadonly()}
on:click={clipboard.actions.paste}
disabled={!$pasteAllowed}
on:click={() => dispatch("paste")}
on:click={menu.actions.close}
>
Paste
@ -148,7 +143,7 @@
<MenuItem
icon="Duplicate"
disabled={isNewRow || !$config.canAddRows}
on:click={duplicate}
on:click={duplicateRow}
>
Duplicate row
</MenuItem>

View File

@ -1,20 +1,79 @@
import { writable, get } from "svelte/store"
import { derived, writable, get } from "svelte/store"
import { Helpers } from "@budibase/bbui"
export const createStores = () => {
const copiedCell = writable(null)
const clipboard = writable({
value: null,
multiCellMode: false,
})
return {
copiedCell,
clipboard,
}
}
export const deriveStores = context => {
const { clipboard, focusedCellAPI, selectedCellCount } = context
const copyAllowed = derived(focusedCellAPI, $focusedCellAPI => {
return $focusedCellAPI != null
})
const pasteAllowed = derived(
[clipboard, focusedCellAPI, selectedCellCount],
([$clipboard, $focusedCellAPI, $selectedCellCount]) => {
if ($clipboard.value == null || !$focusedCellAPI) {
return false
}
// Prevent pasting into a single cell, if we have a single cell value and
// this cell is readonly
const multiCellPaste = $selectedCellCount > 1
if (
!$clipboard.multiCellMode &&
!multiCellPaste &&
$focusedCellAPI.isReadonly()
) {
return false
}
return true
}
)
return {
copyAllowed,
pasteAllowed,
}
}
export const createActions = context => {
const { copiedCell, focusedCellAPI } = context
const {
clipboard,
selectedCellCount,
focusedCellAPI,
copyAllowed,
pasteAllowed,
} = context
const copy = () => {
const value = get(focusedCellAPI)?.getValue()
copiedCell.set(value)
if (!get(copyAllowed)) {
return
}
const $selectedCellCount = get(selectedCellCount)
const $focusedCellAPI = get(focusedCellAPI)
const multiCellMode = $selectedCellCount > 1
// Multiple values to copy
if (multiCellMode) {
// TODO
return
}
// Single value to copy
const value = $focusedCellAPI.getValue()
clipboard.set({
value,
multiCellMode,
})
// Also copy a stringified version to the clipboard
let stringified = ""
@ -26,15 +85,38 @@ export const createActions = context => {
}
const paste = () => {
const $copiedCell = get(copiedCell)
if (!get(pasteAllowed)) {
return
}
const $clipboard = get(clipboard)
const $focusedCellAPI = get(focusedCellAPI)
if ($copiedCell != null && $focusedCellAPI) {
$focusedCellAPI.setValue($copiedCell)
if ($clipboard.value == null || !$focusedCellAPI) {
return
}
// Check if we're pasting into one or more cells
const $selectedCellCount = get(selectedCellCount)
const multiCellPaste = $selectedCellCount > 1
if ($clipboard.multiCellMode) {
if (multiCellPaste) {
// Multi to multi (only paste selected cells)
} else {
// Multi to single (expand to paste all values)
}
} else {
if (multiCellPaste) {
// Single to multi (duplicate value in all selected cells)
} else {
// Single to single
$focusedCellAPI.setValue($clipboard.value)
}
}
}
return {
clipboard: {
...clipboard,
actions: {
copy,
paste,

View File

@ -41,11 +41,11 @@ const DependencyOrderedStores = [
Users,
Menu,
Pagination,
Selection,
Clipboard,
Config,
Notifications,
Cache,
Selection,
]
export const attachStores = context => {