Browse Source

Merge pull request #6922 from nocodb/nc-fix/grid-context-menu-shared-view

Added copy/cell selection in share view grid
pull/6930/head
Raju Udava 1 year ago committed by GitHub
parent
commit
2576e7df1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      packages/nc-gui/components/cell/DatePicker.vue
  2. 10
      packages/nc-gui/components/cell/DateTimePicker.vue
  3. 8
      packages/nc-gui/components/cell/TimePicker.vue
  4. 8
      packages/nc-gui/components/cell/YearPicker.vue
  5. 65
      packages/nc-gui/components/smartsheet/grid/Table.vue
  6. 2
      packages/nc-gui/composables/useMultiSelect/index.ts
  7. 8
      tests/playwright/pages/Dashboard/common/Cell/index.ts

10
packages/nc-gui/components/cell/DatePicker.vue

@ -185,6 +185,14 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
} }
}) })
const isOpen = computed(() => {
if (readOnly.value) return false
return ((readOnly.value || (localState.value && isPk)) && !active.value && !editable.value) || isLockedMode.value
? false
: open.value
})
// use the default date picker open sync only to close the picker // use the default date picker open sync only to close the picker
const updateOpen = (next: boolean) => { const updateOpen = (next: boolean) => {
if (open.value && !next) { if (open.value && !next) {
@ -222,7 +230,7 @@ const clickHandler = () => {
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"
:dropdown-class-name="`${randomClass} nc-picker-date ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-date ${open ? 'active' : ''}`"
:open="((readOnly || (localState && isPk)) && !active && !editable) || isLockedMode ? false : open" :open="isOpen"
@click="clickHandler" @click="clickHandler"
@update:open="updateOpen" @update:open="updateOpen"
> >

10
packages/nc-gui/components/cell/DateTimePicker.vue

@ -123,6 +123,14 @@ const localState = computed({
const open = ref(false) const open = ref(false)
const isOpen = computed(() => {
if (readOnly.value) return false
return readOnly.value || (localState.value && isPk) || isLockedMode.value
? false
: open.value && (active.value || editable.value)
})
const randomClass = `picker_${Math.floor(Math.random() * 99999)}` const randomClass = `picker_${Math.floor(Math.random() * 99999)}`
watch( watch(
open, open,
@ -270,7 +278,7 @@ const isColDisabled = computed(() => {
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"
:dropdown-class-name="`${randomClass} nc-picker-datetime ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-datetime ${open ? 'active' : ''}`"
:open="readOnly || (localState && isPk) || isLockedMode ? false : open && (active || editable)" :open="isOpen"
@click="clickHandler" @click="clickHandler"
@ok="open = !open" @ok="open = !open"
> >

8
packages/nc-gui/components/cell/TimePicker.vue

@ -101,6 +101,12 @@ const placeholder = computed(() => {
} }
}) })
const isOpen = computed(() => {
if (readOnly.value) return false
return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
})
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
@ -129,7 +135,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"
:open="(readOnly || (localState && isPk)) && !active && !editable ? false : open" :open="isOpen"
:popup-class-name="`${randomClass} nc-picker-time ${open ? 'active' : ''}`" :popup-class-name="`${randomClass} nc-picker-time ${open ? 'active' : ''}`"
@click="open = (active || editable) && !open" @click="open = (active || editable) && !open"
@ok="open = !open" @ok="open = !open"

8
packages/nc-gui/components/cell/YearPicker.vue

@ -88,6 +88,12 @@ const placeholder = computed(() => {
} }
}) })
const isOpen = computed(() => {
if (readOnly.value) return false
return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
})
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
@ -114,7 +120,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="(!readOnly && !localState && !isPk) || isEditColumn" :allow-clear="(!readOnly && !localState && !isPk) || isEditColumn"
:input-read-only="true" :input-read-only="true"
:open="(readOnly || (localState && isPk)) && !active && !editable ? false : open" :open="isOpen"
:dropdown-class-name="`${randomClass} nc-picker-year ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-year ${open ? 'active' : ''}`"
@click="open = (active || editable) && !open" @click="open = (active || editable) && !open"
@change="open = (active || editable) && !open" @change="open = (active || editable) && !open"

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

@ -181,7 +181,7 @@ const gridRect = useElementBounding(gridWrapper)
// #Permissions // #Permissions
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const hasEditPermission = computed(() => isUIAllowed('dataEdit')) const hasEditPermission = computed(() => isUIAllowed('dataEdit') && !isLocked.value)
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value) const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value)
const { onDrag, onDragStart, draggedCol, dragColPlaceholderDomRef, toBeDroppedColId } = useColumnDrag({ const { onDrag, onDragStart, draggedCol, dragColPlaceholderDomRef, toBeDroppedColId } = useColumnDrag({
@ -223,9 +223,7 @@ const _contextMenu = ref(false)
const contextMenu = computed({ const contextMenu = computed({
get: () => _contextMenu.value, get: () => _contextMenu.value,
set: (val) => { set: (val) => {
if (hasEditPermission.value) { _contextMenu.value = val
_contextMenu.value = val
}
}, },
}) })
const contextMenuClosing = ref(false) const contextMenuClosing = ref(false)
@ -1549,14 +1547,12 @@ onKeyStroke('ArrowDown', onDown)
<SmartsheetTableDataCell <SmartsheetTableDataCell
v-for="(columnObj, colIndex) of fields" v-for="(columnObj, colIndex) of fields"
:key="columnObj.id" :key="columnObj.id"
class="cell relative nc-grid-cell" class="cell relative nc-grid-cell cursor-pointer"
:class="{ :class="{
'cursor-pointer': hasEditPermission, 'active': isCellSelected(rowIndex, colIndex),
'active': hasEditPermission && isCellSelected(rowIndex, colIndex),
'active-cell': 'active-cell':
hasEditPermission && (activeCell.row === rowIndex && activeCell.col === colIndex) ||
((activeCell.row === rowIndex && activeCell.col === colIndex) || (selectedRange._start?.row === rowIndex && selectedRange._start?.col === colIndex),
(selectedRange._start?.row === rowIndex && selectedRange._start?.col === colIndex)),
'last-cell': 'last-cell':
rowIndex === (isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) && rowIndex === (isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) &&
colIndex === (isNaN(selectedRange.end.col) ? activeCell.col : selectedRange.end.col), colIndex === (isNaN(selectedRange.end.col) ? activeCell.col : selectedRange.end.col),
@ -1594,7 +1590,7 @@ onKeyStroke('ArrowDown', onDown)
:column="columnObj" :column="columnObj"
:active="activeCell.col === colIndex && activeCell.row === rowIndex" :active="activeCell.col === colIndex && activeCell.row === rowIndex"
:row="row" :row="row"
:read-only="readOnly" :read-only="!hasEditPermission"
@navigate="onNavigate" @navigate="onNavigate"
@save="updateOrSaveRow?.(row, '', state)" @save="updateOrSaveRow?.(row, '', state)"
/> />
@ -1608,7 +1604,7 @@ onKeyStroke('ArrowDown', onDown)
" "
:row-index="rowIndex" :row-index="rowIndex"
:active="activeCell.col === colIndex && activeCell.row === rowIndex" :active="activeCell.col === colIndex && activeCell.row === rowIndex"
:read-only="readOnly" :read-only="!hasEditPermission"
@update:edit-enabled="editEnabled = $event" @update:edit-enabled="editEnabled = $event"
@save="updateOrSaveRow?.(row, columnObj.title, state)" @save="updateOrSaveRow?.(row, columnObj.title, state)"
@navigate="onNavigate" @navigate="onNavigate"
@ -1658,7 +1654,7 @@ onKeyStroke('ArrowDown', onDown)
/> />
</div> </div>
<template v-if="!isLocked && hasEditPermission" #overlay> <template #overlay>
<NcMenu class="!rounded !py-0" @click="contextMenu = false"> <NcMenu class="!rounded !py-0" @click="contextMenu = false">
<NcMenuItem <NcMenuItem
v-if="isEeUI && !contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected)" v-if="isEeUI && !contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected)"
@ -1709,6 +1705,7 @@ onKeyStroke('ArrowDown', onDown)
<NcMenuItem <NcMenuItem
v-if=" v-if="
contextMenuTarget && contextMenuTarget &&
hasEditPermission &&
selectedRange.isSingleCell() && selectedRange.isSingleCell() &&
(isLinksOrLTAR(fields[contextMenuTarget.col]) || !isVirtualCol(fields[contextMenuTarget.col])) (isLinksOrLTAR(fields[contextMenuTarget.col]) || !isVirtualCol(fields[contextMenuTarget.col]))
" "
@ -1722,7 +1719,7 @@ onKeyStroke('ArrowDown', onDown)
<!-- Clear cell --> <!-- Clear cell -->
<NcMenuItem <NcMenuItem
v-else-if="contextMenuTarget" v-else-if="contextMenuTarget && hasEditPermission"
v-e="['a:row:clear-range']" v-e="['a:row:clear-range']"
class="nc-base-menu-item" class="nc-base-menu-item"
@click="clearSelectedRangeOfCells()" @click="clearSelectedRangeOfCells()"
@ -1731,7 +1728,9 @@ onKeyStroke('ArrowDown', onDown)
{{ $t('general.clear') }} {{ $t('general.clear') }}
</NcMenuItem> </NcMenuItem>
<template v-if="contextMenuTarget && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode"> <template
v-if="contextMenuTarget && !isLocked && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode"
>
<NcDivider /> <NcDivider />
<NcMenuItem v-e="['a:row:comment']" class="nc-base-menu-item" @click="commentRow(contextMenuTarget.row)"> <NcMenuItem v-e="['a:row:comment']" class="nc-base-menu-item" @click="commentRow(contextMenuTarget.row)">
<MdiMessageOutline class="h-4 w-4" /> <MdiMessageOutline class="h-4 w-4" />
@ -1739,28 +1738,30 @@ onKeyStroke('ArrowDown', onDown)
{{ $t('general.comment') }} {{ $t('general.comment') }}
</NcMenuItem> </NcMenuItem>
</template> </template>
<NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected))" /> <template v-if="hasEditPermission">
<NcMenuItem <NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected))" />
v-if="contextMenuTarget && (selectedRange.isSingleCell() || selectedRange.isSingleRow())"
v-e="['a:row:delete']"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
@click="confirmDeleteRow(contextMenuTarget.row)"
>
<GeneralIcon icon="delete" />
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</NcMenuItem>
<div v-else-if="contextMenuTarget && deleteRangeOfRows">
<NcMenuItem <NcMenuItem
v-if="contextMenuTarget && (selectedRange.isSingleCell() || selectedRange.isSingleRow())"
v-e="['a:row:delete']" v-e="['a:row:delete']"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50" class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
@click="deleteSelectedRangeOfRows" @click="confirmDeleteRow(contextMenuTarget.row)"
> >
<GeneralIcon icon="delete" class="text-gray-500 text-red-600" /> <GeneralIcon icon="delete" />
<!-- Delete Rows --> <!-- Delete Row -->
{{ $t('activity.deleteRows') }} {{ $t('activity.deleteRow') }}
</NcMenuItem> </NcMenuItem>
</div> <div v-else-if="contextMenuTarget && deleteRangeOfRows">
<NcMenuItem
v-e="['a:row:delete']"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
@click="deleteSelectedRangeOfRows"
>
<GeneralIcon icon="delete" class="text-gray-500 text-red-600" />
<!-- Delete Rows -->
{{ $t('activity.deleteRows') }}
</NcMenuItem>
</div>
</template>
</NcMenu> </NcMenu>
</template> </template>
</NcDropdown> </NcDropdown>

2
packages/nc-gui/composables/useMultiSelect/index.ts

@ -116,6 +116,8 @@ export function useMultiSelect(
if (typeof textToCopy === 'object') { if (typeof textToCopy === 'object') {
textToCopy = JSON.stringify(textToCopy) textToCopy = JSON.stringify(textToCopy)
} else {
textToCopy = textToCopy.toString()
} }
if (columnObj.uidt === UITypes.Formula) { if (columnObj.uidt === UITypes.Formula) {

8
tests/playwright/pages/Dashboard/common/Cell/index.ts

@ -381,25 +381,25 @@ export class CellPageObject extends BasePage {
async verifyRoleAccess(param: { role: string }) { async verifyRoleAccess(param: { role: string }) {
const role = param.role.toLowerCase(); const role = param.role.toLowerCase();
const count = role === 'creator' || role === 'editor' || role === 'owner' ? 1 : 0; const isEditAccess = role === 'creator' || role === 'editor' || role === 'owner';
// normal text cell // normal text cell
const cell = this.get({ index: 0, columnHeader: 'Country' }); const cell = this.get({ index: 0, columnHeader: 'Country' });
// editable cell // editable cell
await cell.dblclick(); await cell.dblclick();
await expect(cell.locator(`input`)).toHaveCount(count); await expect(cell.locator(`input`)).toHaveCount(isEditAccess ? 1 : 0);
// press escape to close the input // press escape to close the input
await cell.press('Escape'); await cell.press('Escape');
await cell.press('Escape'); await cell.press('Escape');
await cell.click({ button: 'right', clickCount: 1 }); await cell.click({ button: 'right', clickCount: 1 });
await expect(this.rootPage.locator(`.nc-dropdown-grid-context-menu:visible`)).toHaveCount(count); await expect(this.rootPage.locator(`.nc-dropdown-grid-context-menu:visible`)).toHaveCount(1);
// virtual cell // virtual cell
const vCell = this.get({ index: 0, columnHeader: 'Cities' }); const vCell = this.get({ index: 0, columnHeader: 'Cities' });
await vCell.hover(); await vCell.hover();
// in-cell add // in-cell add
await expect(vCell.locator('.nc-action-icon.nc-plus:visible')).toHaveCount(count); await expect(vCell.locator('.nc-action-icon.nc-plus:visible')).toHaveCount(isEditAccess ? 1 : 0);
// virtual cell link text // virtual cell link text
const linkText = await getTextExcludeIconText(vCell); const linkText = await getTextExcludeIconText(vCell);

Loading…
Cancel
Save