From 502c2541e576de262232bd6478f293a7743bc314 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 21 Jun 2024 15:17:49 +0100 Subject: [PATCH] Clean up and improve copy/paste flows --- .../controls/BulkDuplicationHandler.svelte | 5 - .../grid/controls/ClipboardHandler.svelte | 45 ++++++++ .../src/components/grid/layout/Grid.svelte | 2 + .../grid/overlays/KeyboardManager.svelte | 7 +- .../grid/overlays/MenuOverlay.svelte | 39 +++---- .../src/components/grid/stores/clipboard.js | 100 ++++++++++++++++-- .../src/components/grid/stores/index.js | 2 +- 7 files changed, 158 insertions(+), 42 deletions(-) create mode 100644 packages/frontend-core/src/components/grid/controls/ClipboardHandler.svelte diff --git a/packages/frontend-core/src/components/grid/controls/BulkDuplicationHandler.svelte b/packages/frontend-core/src/components/grid/controls/BulkDuplicationHandler.svelte index bc321fffde..4fbf79b81c 100644 --- a/packages/frontend-core/src/components/grid/controls/BulkDuplicationHandler.svelte +++ b/packages/frontend-core/src/components/grid/controls/BulkDuplicationHandler.svelte @@ -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())) diff --git a/packages/frontend-core/src/components/grid/controls/ClipboardHandler.svelte b/packages/frontend-core/src/components/grid/controls/ClipboardHandler.svelte new file mode 100644 index 0000000000..87b881e8ad --- /dev/null +++ b/packages/frontend-core/src/components/grid/controls/ClipboardHandler.svelte @@ -0,0 +1,45 @@ + + + + + Are you sure you want to paste values into {$selectedCellCount} cells? + + diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index 8496aebd35..6c61fdd45e 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -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} {/if} + diff --git a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte index f38eb8d997..0da90298b6 100644 --- a/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte +++ b/packages/frontend-core/src/components/grid/overlays/KeyboardManager.svelte @@ -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) { diff --git a/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte b/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte index 700670940d..99ecc602a4 100644 --- a/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte +++ b/packages/frontend-core/src/components/grid/overlays/MenuOverlay.svelte @@ -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 @@ 50} - on:click={bulkDuplicate} + on:click={() => dispatch("request-bulk-duplicate")} + on:click={menu.actions.close} > Duplicate {$selectedRowCount} rows dispatch("request-bulk-delete")} + on:click={menu.actions.close} > Delete {$selectedRowCount} rows {:else if $menu.multiCellMode} dispatch("copy")} on:click={menu.actions.close} > Copy dispatch("paste")} on:click={menu.actions.close} > Paste @@ -104,15 +98,16 @@ {:else} dispatch("copy")} on:click={menu.actions.close} > Copy dispatch("paste")} on:click={menu.actions.close} > Paste @@ -148,7 +143,7 @@ Duplicate row diff --git a/packages/frontend-core/src/components/grid/stores/clipboard.js b/packages/frontend-core/src/components/grid/stores/clipboard.js index 200df29902..72bb3fa1ac 100644 --- a/packages/frontend-core/src/components/grid/stores/clipboard.js +++ b/packages/frontend-core/src/components/grid/stores/clipboard.js @@ -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, diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index a261023b2c..011c5fda12 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -41,11 +41,11 @@ const DependencyOrderedStores = [ Users, Menu, Pagination, + Selection, Clipboard, Config, Notifications, Cache, - Selection, ] export const attachStores = context => {