Browse Source

Merge pull request #7207 from nocodb/feat/paste-option-in-table-context-menu

feat: paste option in cell right click context menu
pull/7222/head
Raju Udava 12 months ago committed by GitHub
parent
commit
188dfda9ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components/smartsheet/details/Fields.vue
  2. 17
      packages/nc-gui/components/smartsheet/grid/Table.vue
  3. 2
      packages/nc-gui/components/tabs/Smartsheet.vue
  4. 39
      packages/nc-gui/composables/usePaste.ts
  5. 8
      packages/nc-gui/lang/en.json
  6. 1
      packages/nc-gui/utils/iconUtils.ts
  7. 10
      tests/playwright/pages/Dashboard/Grid/index.ts
  8. 25
      tests/playwright/tests/db/general/cellSelection.spec.ts

2
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -1081,7 +1081,7 @@ const onFieldOptionUpdate = () => {
</Draggable>
</div>
<Transition v-if="!changingField" name="slide-fade">
<div class="border-gray-200 border-l-1 nc-scrollbar-md nc-fields-height !overflow-y-auto">
<div v-if="!changingField" class="border-gray-200 border-l-1 nc-scrollbar-md nc-fields-height !overflow-y-auto">
<SmartsheetColumnEditOrAddProvider
v-if="activeField"
class="p-4 w-[25rem]"

17
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -34,6 +34,7 @@ import {
useI18n,
useMultiSelect,
useNuxtApp,
usePaste,
useRoles,
useRoute,
useSmartsheetStoreOrThrow,
@ -168,6 +169,8 @@ const predictNextFormulas = async () => {
await _predictNextFormulas(meta)
}
const { paste } = usePaste(true)
// #Refs
const rowRefs = ref<any[]>()
@ -1694,6 +1697,20 @@ onKeyStroke('ArrowDown', onDown)
</div>
</NcMenuItem>
<NcMenuItem
v-if="contextMenuTarget"
class="nc-base-menu-item"
data-testid="context-menu-item-paste"
:disabled="isSystemColumn(fields[contextMenuTarget.col])"
@click="paste"
>
<div v-e="['a:row:paste']" class="flex gap-2 items-center">
<GeneralIcon icon="paste" />
<!-- Paste -->
{{ $t('general.paste') }}
</div>
</NcMenuItem>
<!-- Clear cell -->
<NcMenuItem
v-if="

2
packages/nc-gui/components/tabs/Smartsheet.vue

@ -171,7 +171,7 @@ watch([activeViewTitleOrId, activeTableId], () => {
<LazySmartsheetToolbar v-if="!isForm" />
<div class="flex flex-row w-full" style="height: calc(100% - var(--topbar-height))">
<Transition name="layout" mode="out-in">
<div class="flex flex-1 min-h-0 w-3/4">
<div v-if="openedViewsTab === 'view'" class="flex flex-1 min-h-0 w-3/4">
<div class="h-full flex-1 min-w-0 min-h-0 bg-white">
<LazySmartsheetGrid v-if="isGrid || !meta || !activeView" ref="grid" />

39
packages/nc-gui/composables/usePaste.ts

@ -0,0 +1,39 @@
import { useI18n, message } from '#imports'
export const usePaste = (showDialogIfFailed = false) => {
const { t } = useI18n()
const paste = async (): Promise<boolean> => {
try {
// Check if the Clipboard API is supported
if (!navigator.clipboard) return false
// Read text from the clipboard
const clipboardText = await navigator.clipboard.readText()
// Create a new paste event
const pasteEvent = new Event('paste', {
bubbles: false,
cancelable: true,
})
// Attach clipboard data to the event
const clipboardData = {
getData: () => clipboardText || '',
}
Object.defineProperty(pasteEvent, 'clipboardData', { value: clipboardData })
// Dispatch the event on the document
document.dispatchEvent(pasteEvent)
return true
} catch (e) {
if (!showDialogIfFailed) throw new Error(t('msg.error.pasteFromClipboardError'))
message.error(t('msg.error.pasteFromClipboardError'))
return false
}
}
return { paste }
}

8
packages/nc-gui/lang/en.json

@ -188,7 +188,8 @@
"useSurveyMode": "Use Survey Mode",
"shift": "Shift",
"enter": "Enter",
"seconds": "Seconds"
"seconds": "Seconds",
"paste": "Paste"
},
"objects": {
"workspace": "Workspace",
@ -1283,7 +1284,8 @@
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Base not accessible",
"copyToClipboardError": "Failed to copy to clipboard"
"copyToClipboardError": "Failed to copy to clipboard",
"pasteFromClipboardError": "Failed to paste from clipboard"
},
"toast": {
"exportMetadata": "Base metadata exported successfully",
@ -1342,4 +1344,4 @@
"roleUpdated": "Role updated successfully"
}
}
}
}

1
packages/nc-gui/utils/iconUtils.ts

@ -452,6 +452,7 @@ export const iconMap = {
role_no_access: NoAccess,
commentHere: NcCommentHere,
fileImage: FileImageIcon,
paste: h('span', { class: 'material-symbols' }, 'content_paste'),
}
export const getMdiIcon = (type: string): any => {

10
tests/playwright/pages/Dashboard/Grid/index.ts

@ -450,4 +450,14 @@ export class GridPage extends BasePage {
}
return text;
}
async pasteWithMouse({ index, columnHeader }: CellProps) {
await this.cell.get({ index, columnHeader }).scrollIntoViewIfNeeded();
await this.cell.get({ index, columnHeader }).click({ button: 'right' });
await this.get().page().getByTestId('context-menu-item-paste').click();
// kludge: wait for paste to complete
await this.rootPage.waitForTimeout(1000);
}
}

25
tests/playwright/tests/db/general/cellSelection.spec.ts

@ -28,16 +28,34 @@ test.describe('Verify cell selection', () => {
expect(await grid.selectedCount()).toBe(9);
await dashboard.closeAllTabs();
// #2 when copied with clipboard, it copies correct text
// #2 when copied with clipboard, it copies correct text and paste
const verifyPastedData = async ({ index }: { index: number }): Promise<void> => {
// FirstName column
let cellText: string[] = ['MARY', 'PATRICIA'];
for (let i = index; i <= index + 1; i++) {
await grid.cell.verify({ index: i, columnHeader: 'FirstName', value: cellText[i - index] });
}
// LastName column
cellText = ['SMITH', 'JOHNSON'];
for (let i = index; i <= index + 1; i++) {
await grid.cell.verify({ index: i, columnHeader: 'LastName', value: cellText[i - index] });
}
};
await dashboard.treeView.openTable({ title: 'Customer' });
await grid.selectRange({
start: { index: 0, columnHeader: 'FirstName' },
end: { index: 1, columnHeader: 'LastName' },
});
expect(await grid.copyWithKeyboard()).toBe('MARY\tSMITH\n' + 'PATRICIA\tJOHNSON');
await grid.pasteWithMouse({ index: 2, columnHeader: 'FirstName' });
await verifyPastedData({ index: 2 });
await dashboard.closeAllTabs();
// #3 when copied with mouse, it copies correct text
// #3 when copied with mouse, it copies correct text and paste
await dashboard.treeView.openTable({ title: 'Customer' });
await grid.selectRange({
start: { index: 0, columnHeader: 'FirstName' },
@ -46,6 +64,9 @@ test.describe('Verify cell selection', () => {
expect(await grid.copyWithMouse({ index: 0, columnHeader: 'FirstName' })).toBe(
'MARY\tSMITH\n' + 'PATRICIA\tJOHNSON'
);
await grid.pasteWithMouse({ index: 4, columnHeader: 'FirstName' });
await verifyPastedData({ index: 4 });
await dashboard.closeAllTabs();
});

Loading…
Cancel
Save