Browse Source

Merge pull request #5724 from nocodb/enhancement/hm-mm-cells

enhancement: hm / mm cells
pull/5820/head
Raju Udava 2 years ago committed by GitHub
parent
commit
d50f6d2a5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components/smartsheet/Cell.vue
  2. 17
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  3. 12
      packages/nc-gui/components/virtual-cell/HasMany.vue
  4. 12
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  5. 49
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  6. 4
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  7. 8
      packages/nc-gui/components/virtual-cell/components/ListItems.vue
  8. 2
      tests/playwright/package-lock.json
  9. 5
      tests/playwright/pages/Dashboard/Grid/Column/LTAR/ChildList.ts
  10. 4
      tests/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts
  11. 2
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  12. 1
      tests/playwright/tests/db/columnRelationalExtendedTests.spec.ts

2
packages/nc-gui/components/smartsheet/Cell.vue

@ -193,7 +193,7 @@ onUnmounted(() => {
`nc-cell-${(column?.uidt || 'default').toLowerCase()}`, `nc-cell-${(column?.uidt || 'default').toLowerCase()}`,
{ 'text-blue-600': isPrimary(column) && !props.virtual && !isForm }, { 'text-blue-600': isPrimary(column) && !props.virtual && !isForm },
{ 'nc-grid-numeric-cell': isGrid && !isForm && isNumericField }, { 'nc-grid-numeric-cell': isGrid && !isForm && isNumericField },
{ 'h-[40px]': !props.editEnabled && isForm && !isSurveyForm && !isAttachment(column) }, { 'h-[40px]': !props.editEnabled && isForm && !isSurveyForm && !isAttachment(column) && !props.virtual },
]" ]"
@keydown.enter.exact="navigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="navigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="navigate(NavigateDir.PREV, $event)" @keydown.shift.enter.exact="navigate(NavigateDir.PREV, $event)"

17
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -45,7 +45,7 @@ const listItemsDlg = ref(false)
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow() const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
const { loadRelatedTableMeta, relatedTableDisplayValueProp, unlink } = useProvideLTARStore( const { relatedTableMeta, loadRelatedTableMeta, relatedTableDisplayValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>, column as Ref<Required<ColumnType>>,
row, row,
isNew, isNew,
@ -81,13 +81,24 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
break break
} }
}) })
const belongsToColumn = computed(
() =>
relatedTableMeta.value?.columns?.find((c: any) => c.title === relatedTableDisplayValueProp.value) as ColumnType | undefined,
)
</script> </script>
<template> <template>
<div class="flex w-full chips-wrapper items-center" :class="{ active }"> <div class="flex w-full chips-wrapper items-center" :class="{ active }">
<div class="chips flex items-center flex-1"> <div class="chips flex items-center flex-1">
<template v-if="value && relatedTableDisplayValueProp"> <template v-if="value && relatedTableDisplayValueProp">
<VirtualCellComponentsItemChip :item="value" :value="value[relatedTableDisplayValueProp]" @unlink="unlinkRef(value)" /> <VirtualCellComponentsItemChip
:item="value"
:value="value[relatedTableDisplayValueProp]"
:column="belongsToColumn"
:show-unlink-button="true"
@unlink="unlinkRef(value)"
/>
</template> </template>
</div> </div>
@ -102,7 +113,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
/> />
</div> </div>
<LazyVirtualCellComponentsListItems v-model="listItemsDlg" @attach-record="listItemsDlg = true" /> <LazyVirtualCellComponentsListItems v-model="listItemsDlg" :column="belongsToColumn" @attach-record="listItemsDlg = true" />
</div> </div>
</template> </template>

12
packages/nc-gui/components/virtual-cell/HasMany.vue

@ -43,7 +43,7 @@ const { isUIAllowed } = useUIPermission()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow() const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
const { loadRelatedTableMeta, relatedTableDisplayValueProp, unlink } = useProvideLTARStore( const { relatedTableMeta, loadRelatedTableMeta, relatedTableDisplayValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>, column as Ref<Required<ColumnType>>,
row, row,
isNew, isNew,
@ -81,6 +81,11 @@ const unlinkRef = async (rec: Record<string, any>) => {
} }
} }
const hasManyColumn = computed(
() =>
relatedTableMeta.value?.columns?.find((c: any) => c.title === relatedTableDisplayValueProp.value) as ColumnType | undefined,
)
const onAttachRecord = () => { const onAttachRecord = () => {
childListDlg.value = false childListDlg.value = false
listItemsDlg.value = true listItemsDlg.value = true
@ -106,6 +111,8 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
:key="i" :key="i"
:item="cell.item" :item="cell.item"
:value="cell.value" :value="cell.value"
:column="hasManyColumn"
:show-unlink-button="true"
@unlink="unlinkRef(cell.item)" @unlink="unlinkRef(cell.item)"
/> />
@ -131,11 +138,12 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</div> </div>
</template> </template>
<LazyVirtualCellComponentsListItems v-model="listItemsDlg" /> <LazyVirtualCellComponentsListItems v-model="listItemsDlg" :column="hasManyColumn" />
<LazyVirtualCellComponentsListChildItems <LazyVirtualCellComponentsListChildItems
v-model="childListDlg" v-model="childListDlg"
:cell-value="localCellValue" :cell-value="localCellValue"
:column="hasManyColumn"
@attach-record="onAttachRecord" @attach-record="onAttachRecord"
/> />
</div> </div>

12
packages/nc-gui/components/virtual-cell/ManyToMany.vue

@ -45,7 +45,7 @@ const { isUIAllowed } = useUIPermission()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow() const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
const { loadRelatedTableMeta, relatedTableDisplayValueProp, unlink } = useProvideLTARStore( const { relatedTableMeta, loadRelatedTableMeta, relatedTableDisplayValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>, column as Ref<Required<ColumnType>>,
row, row,
isNew, isNew,
@ -96,6 +96,11 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
break break
} }
}) })
const m2mColumn = computed(
() =>
relatedTableMeta.value?.columns?.find((c: any) => c.title === relatedTableDisplayValueProp.value) as ColumnType | undefined,
)
</script> </script>
<template> <template>
@ -108,6 +113,8 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
:key="i" :key="i"
:item="cell.item" :item="cell.item"
:value="cell.value" :value="cell.value"
:column="m2mColumn"
:show-unlink-button="true"
@unlink="unlinkRef(cell.item)" @unlink="unlinkRef(cell.item)"
/> />
@ -133,11 +140,12 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</div> </div>
</template> </template>
<LazyVirtualCellComponentsListItems v-model="listItemsDlg" /> <LazyVirtualCellComponentsListItems v-model="listItemsDlg" :column="m2mColumn" />
<LazyVirtualCellComponentsListChildItems <LazyVirtualCellComponentsListChildItems
v-model="childListDlg" v-model="childListDlg"
:cell-value="localCellValue" :cell-value="localCellValue"
:column="m2mColumn"
@attach-record="onAttachRecord" @attach-record="onAttachRecord"
/> />
</div> </div>

49
packages/nc-gui/components/virtual-cell/components/ItemChip.vue

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { import {
ActiveCellInj, ActiveCellInj,
IsFormInj, IsFormInj,
@ -6,6 +7,7 @@ import {
ReadonlyInj, ReadonlyInj,
iconMap, iconMap,
inject, inject,
isAttachment,
ref, ref,
renderValue, renderValue,
useExpandedFormDetached, useExpandedFormDetached,
@ -15,9 +17,11 @@ import {
interface Props { interface Props {
value?: string | number | boolean value?: string | number | boolean
item?: any item?: any
column: any
showUnlinkButton: boolean
} }
const { value, item } = defineProps<Props>() const { value, item, column, showUnlinkButton } = defineProps<Props>()
const emit = defineEmits(['unlink']) const emit = defineEmits(['unlink'])
@ -56,13 +60,46 @@ export default {
<template> <template>
<div <div
class="chip group py-1 px-2 mr-1 my-1 flex items-center bg-blue-100/60 hover:bg-blue-100/40 rounded-[2px]" class="chip group mr-1 my-1 flex items-center rounded-[2px] flex-row"
:class="{ active }" :class="{ active, 'border-1 py-1 px-2': isAttachment(column) }"
@click="openExpandedForm" @click="openExpandedForm"
> >
<span class="name">{{ renderValue(value) }}</span> <span class="name">
<!-- Render virtual cell -->
<div v-show="active || isForm" v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')" class="flex items-center"> <div v-if="isVirtualCol(column)">
<template v-if="column.uidt === UITypes.LinkToAnotherRecord">
<LazySmartsheetVirtualCell :edit-enabled="false" :model-value="value" :column="column" :read-only="true" />
</template>
<LazySmartsheetVirtualCell v-else :edit-enabled="false" :read-only="true" :model-value="value" :column="column" />
</div>
<!-- Render normal cell -->
<template v-else>
<div v-if="isAttachment(column) && value && !Array.isArray(value) && typeof value === 'object'">
<LazySmartsheetCell :model-value="value" :column="column" :edit-enabled="false" :read-only="true" />
</div>
<!-- For attachment cell avoid adding chip style -->
<template v-else>
<div
class="min-w-max"
:class="{
'px-1 rounded-full flex-1': !isAttachment(column),
'border-gray-200 rounded border-1': ![UITypes.Attachment, UITypes.MultiSelect, UITypes.SingleSelect].includes(
column.uidt,
),
}"
>
<LazySmartsheetCell :model-value="value" :column="column" :edit-enabled="false" :virtual="true" :read-only="true" />
</div>
</template>
</template>
</span>
<div
v-show="active || isForm"
v-if="showUnlinkButton && !readOnly && !isLocked && isUIAllowed('xcDatatableEditable')"
class="flex items-center"
>
<component <component
:is="iconMap.closeThick" :is="iconMap.closeThick"
class="nc-icon unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" class="nc-icon unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500"

4
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -19,7 +19,7 @@ import {
useVModel, useVModel,
} from '#imports' } from '#imports'
const props = defineProps<{ modelValue?: boolean; cellValue: any }>() const props = defineProps<{ modelValue?: boolean; cellValue: any; column: any }>()
const emit = defineEmits(['update:modelValue', 'attachRecord']) const emit = defineEmits(['update:modelValue', 'attachRecord'])
@ -148,7 +148,7 @@ const onClick = (row: Row) => {
> >
<div class="flex items-center"> <div class="flex items-center">
<div class="flex-1 overflow-hidden min-w-0"> <div class="flex-1 overflow-hidden min-w-0">
{{ renderValue(row[relatedTableDisplayValueProp]) }} <VirtualCellComponentsItemChip :value="row[relatedTableDisplayValueProp]" :column="props.column" />
<span class="text-gray-400 text-[11px] ml-1">(Primary key : {{ getRelatedTableRowId(row) }})</span> <span class="text-gray-400 text-[11px] ml-1">(Primary key : {{ getRelatedTableRowId(row) }})</span>
</div> </div>

8
packages/nc-gui/components/virtual-cell/components/ListItems.vue

@ -19,7 +19,7 @@ import {
useVModel, useVModel,
} from '#imports' } from '#imports'
const props = defineProps<{ modelValue: boolean }>() const props = defineProps<{ modelValue: boolean; column: any }>()
const emit = defineEmits(['update:modelValue', 'addNewRecord']) const emit = defineEmits(['update:modelValue', 'addNewRecord'])
@ -229,7 +229,11 @@ watch(vModel, (nextVal) => {
:class="{ 'nc-selected-row': selectedRowIndex === i }" :class="{ 'nc-selected-row': selectedRowIndex === i }"
@click="linkRow(refRow)" @click="linkRow(refRow)"
> >
{{ renderValue(refRow[relatedTableDisplayValueProp]) }} <VirtualCellComponentsItemChip
:value="refRow[relatedTableDisplayValueProp]"
:column="props.column"
:show-unlink-button="false"
/>
<span class="hidden group-hover:(inline) text-gray-400 text-[11px] ml-1"> <span class="hidden group-hover:(inline) text-gray-400 text-[11px] ml-1">
({{ $t('labels.primaryKey') }} : {{ getRelatedTableRowId(refRow) }}) ({{ $t('labels.primaryKey') }} : {{ getRelatedTableRowId(refRow) }})
</span> </span>

2
tests/playwright/package-lock.json generated

@ -39,7 +39,7 @@
} }
}, },
"../../packages/nocodb-sdk": { "../../packages/nocodb-sdk": {
"version": "0.107.0-beta.1", "version": "0.107.5",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

5
tests/playwright/pages/Dashboard/Grid/Column/LTAR/ChildList.ts

@ -31,7 +31,10 @@ export class ChildList extends BasePage {
const childCards = await childList.count(); const childCards = await childList.count();
await expect(childCards).toEqual(cardCount); await expect(childCards).toEqual(cardCount);
for (let i = 0; i < cardCount; i++) { for (let i = 0; i < cardCount; i++) {
await expect(await childList.nth(i).textContent()).toContain(cardTitle[i]); await childList.nth(i).locator('.name').waitFor({ state: 'visible' });
await childList.nth(i).locator('.name').scrollIntoViewIfNeeded();
await this.rootPage.waitForTimeout(100);
await expect(await childList.nth(i).locator('.name').textContent()).toContain(cardTitle[i]);
// icon: unlink // icon: unlink
// icon: delete // icon: delete
await expect( await expect(

4
tests/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts

@ -29,7 +29,9 @@ export class LinkRecord extends BasePage {
const childCards = await childList.count(); const childCards = await childList.count();
await expect(childCards).toEqual(cardTitle.length); await expect(childCards).toEqual(cardTitle.length);
for (let i = 0; i < cardTitle.length; i++) { for (let i = 0; i < cardTitle.length; i++) {
await expect(await childList.nth(i).textContent()).toContain(cardTitle[i]); await childList.nth(i).locator('.name').scrollIntoViewIfNeeded();
await childList.nth(i).locator('.name').waitFor({ state: 'visible' });
await expect(await childList.nth(i).locator('.name').textContent()).toContain(cardTitle[i]);
} }
} }
} }

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

@ -286,6 +286,8 @@ export class CellPageObject extends BasePage {
// verify only the elements that are passed in // verify only the elements that are passed in
for (let i = 0; i < value.length; ++i) { for (let i = 0; i < value.length; ++i) {
await chips.nth(i).locator('.name').waitFor({ state: 'visible' });
await chips.nth(i).locator('.name').scrollIntoViewIfNeeded();
await expect(await chips.nth(i).locator('.name')).toHaveText(value[i]); await expect(await chips.nth(i).locator('.name')).toHaveText(value[i]);
} }

1
tests/playwright/tests/db/columnRelationalExtendedTests.spec.ts

@ -1,7 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../setup';
import { isPg } from '../../setup/db';
test.describe('Relational Columns', () => { test.describe('Relational Columns', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

Loading…
Cancel
Save