diff --git a/packages/nc-gui/assets/nc-icons/link.svg b/packages/nc-gui/assets/nc-icons/link.svg
new file mode 100644
index 0000000000..1207f4b97f
--- /dev/null
+++ b/packages/nc-gui/assets/nc-icons/link.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/nc-gui/assets/style.scss b/packages/nc-gui/assets/style.scss
index 1986dd926c..5dee33bf42 100644
--- a/packages/nc-gui/assets/style.scss
+++ b/packages/nc-gui/assets/style.scss
@@ -33,7 +33,7 @@
}
.anticon-check-circle {
- @apply !relative top-[-1px] left-0;
+ @apply !relative top-[-1px] left-0;
}
html,
@@ -580,10 +580,6 @@ input[type='number'] {
@apply !block;
}
-.ant-card-body {
- @apply !p-2;
-}
-
.ant-pagination .ant-pagination-item-link-icon {
@apply !block !py-1.5;
}
diff --git a/packages/nc-gui/components.d.ts b/packages/nc-gui/components.d.ts
index ec39c7fb6f..0e78a9c6a6 100644
--- a/packages/nc-gui/components.d.ts
+++ b/packages/nc-gui/components.d.ts
@@ -159,6 +159,7 @@ declare module '@vue/runtime-core' {
MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default']
MiCircleWarning: typeof import('~icons/mi/circle-warning')['default']
NcIconsInbox: typeof import('~icons/nc-icons/inbox')['default']
+ PhLink: typeof import('~icons/ph/link')['default']
PhMagnifyingGlassBold: typeof import('~icons/ph/magnifying-glass-bold')['default']
PhTriangleFill: typeof import('~icons/ph/triangle-fill')['default']
RiExternalLinkLine: typeof import('~icons/ri/external-link-line')['default']
diff --git a/packages/nc-gui/components/virtual-cell/components/Header.vue b/packages/nc-gui/components/virtual-cell/components/Header.vue
index da78143a2c..286eb3b9da 100644
--- a/packages/nc-gui/components/virtual-cell/components/Header.vue
+++ b/packages/nc-gui/components/virtual-cell/components/Header.vue
@@ -49,9 +49,9 @@ const relationMeta = computed(() => {
-
+
{{ showHeader ? 'Linked Records' : '' }}
-
+
@@ -108,7 +108,7 @@ const relationMeta = computed(() => {
-
+
diff --git a/packages/nc-gui/components/virtual-cell/components/ListChildItems.vue b/packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
index 766df7082d..a09db62757 100644
--- a/packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
+++ b/packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
@@ -2,7 +2,6 @@
import { type ColumnType, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import type { Row } from '#imports'
import InboxIcon from '~icons/nc-icons/inbox'
-import ColumnIcon from '~icons/nc-icons/column'
import {
ColumnInj,
@@ -12,6 +11,7 @@ import {
computed,
inject,
isPrimary,
+ onKeyStroke,
ref,
useLTARStoreOrThrow,
useSmartsheetRowStoreOrThrow,
@@ -41,6 +41,7 @@ const {
unlink,
isChildrenListLoading,
isChildrenListLinked,
+ isChildrenLoading,
relatedTableMeta,
row,
link,
@@ -112,7 +113,13 @@ watch(
)
watch(expandedFormDlg, () => {
- loadChildrenList()
+ if (!expandedFormDlg.value) {
+ loadChildrenList()
+ }
+})
+
+onKeyStroke('Escape', () => {
+ vModel.value = false
})
@@ -131,6 +138,7 @@ watch(expandedFormDlg, () => {
:relation="relation"
:linked-records="childrenListCount"
:table-title="meta?.title"
+ :show-header="true"
:related-table-title="relatedTableMeta?.title"
:display-value="row.row[displayValueProp]"
/>
@@ -152,6 +160,7 @@ watch(expandedFormDlg, () => {
@focus="isFocused = true"
@blur="isFocused = false"
@keydown.capture.stop
+ @change="childrenListPagination.page = 1"
>
@@ -166,28 +175,60 @@ watch(expandedFormDlg, () => {
}"
class="overflow-scroll nc-scrollbar-md cursor-pointer pr-1"
>
- {
- if (isPublic && !isForm) return
- isNew
- ? unlinkRow(refRow, Number.parseInt(id))
- : isChildrenListLinked[Number.parseInt(id)]
- ? unlinkRow(refRow, Number.parseInt(id))
- : linkRow(refRow, Number.parseInt(id))
- }
- "
- />
+
+
+
+
+ {
+ if (isPublic && !isForm) return
+ isNew
+ ? unlinkRow(refRow, Number.parseInt(id))
+ : isChildrenListLinked[Number.parseInt(id)]
+ ? unlinkRow(refRow, Number.parseInt(id))
+ : linkRow(refRow, Number.parseInt(id))
+ }
+ "
+ />
+
@@ -202,10 +243,7 @@ watch(expandedFormDlg, () => {
No records are linked from table
-
-
- {{ relatedTableMeta?.title }}
-
+ {{ relatedTableMeta?.title }}
{
-
+
{{ childrenListCount || 0 }} records {{ childrenListCount !== 0 ? 'are' : '' }} linked
-
+
{{ state?.[colTitle]?.length || 0 }} records {{ state?.[colTitle]?.length !== 0 ? 'are' : '' }} linked
@@ -238,8 +276,8 @@ watch(expandedFormDlg, () => {
show-less-items
/>
-
-
Cancel
+
+
Finish
{
-
-
+
+
-
+
-
+
-
{{ row[relatedTableDisplayValueProp] }}
-
{{ row[relatedTableDisplayValueProp] }}
+
+ >
+
+ Linked
+
0 && !isPublic && !isForm" class="flex flex-row gap-4 w-10/12">
+
{
v-if="!isForm && !isPublic"
type="text"
size="lg"
- class="!px-2 nc-expand-item !group-hover:block !hidden !absolute right-1 bottom-1"
+ class="!px-2 nc-expand-item !group-hover:block !hidden !border-1 !shadow-sm !border-gray-200 !bg-white !absolute right-3 bottom-3"
+ :class="{
+ '!group-hover:right-1.8 !group-hover:bottom-1.7': fields.length === 0,
+ }"
@click.stop="$emit('expand', row)"
>
diff --git a/packages/nc-gui/components/virtual-cell/components/ListItems.vue b/packages/nc-gui/components/virtual-cell/components/ListItems.vue
index d4a828e3ca..84d93e7a4e 100644
--- a/packages/nc-gui/components/virtual-cell/components/ListItems.vue
+++ b/packages/nc-gui/components/virtual-cell/components/ListItems.vue
@@ -2,13 +2,13 @@
import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import InboxIcon from '~icons/nc-icons/inbox'
-import ColumnIcon from '~icons/nc-icons/column'
import {
ColumnInj,
IsPublicInj,
SaveRowInj,
computed,
inject,
+ onKeyStroke,
ref,
useLTARStoreOrThrow,
useSmartsheetRowStoreOrThrow,
@@ -30,6 +30,7 @@ const {
isChildrenExcludedListLinked,
isChildrenExcludedListLoading,
displayValueProp,
+ isChildrenExcludedLoading,
childrenListCount,
loadChildrenExcludedList,
loadChildrenList,
@@ -138,7 +139,13 @@ const relation = computed(() => {
})
watch(expandedFormDlg, () => {
- loadChildrenExcludedList(rowState.value)
+ if (!expandedFormDlg.value) {
+ loadChildrenExcludedList(rowState.value)
+ }
+})
+
+onKeyStroke('Escape', () => {
+ vModel.value = false
})
@@ -176,6 +183,7 @@ watch(expandedFormDlg, () => {
@focus="isFocused = true"
@blur="isFocused = false"
@keydown.capture.stop
+ @change="childrenExcludedListPagination.page = 1"
>
@@ -202,29 +210,61 @@ watch(expandedFormDlg, () => {
@@ -232,16 +272,13 @@ watch(expandedFormDlg, () => {
There are no records in table
-
-
- {{ relatedTableMeta?.title }}
-
+ {{ relatedTableMeta?.title }}
-
+
{{ relation === 'bt' ? (row.row[relatedTableMeta?.title] ? '1' : 0) : childrenListCount ?? 'No' }} records
{{ childrenListCount !== 0 ? 'are' : '' }} linked
@@ -258,7 +295,7 @@ watch(expandedFormDlg, () => {
show-less-items
/>
-
Cancel
+
Finish
>([])
const isChildrenListLinked = ref>([])
const isChildrenExcludedListLoading = ref>([])
+ const isChildrenExcludedLoading = ref(false)
+
const isChildrenExcludedListLinked = ref>([])
const newRowState = reactive({
@@ -127,6 +131,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
const loadChildrenExcludedList = async (activeState?: any) => {
if (activeState) newRowState.state = activeState
try {
+ isChildrenExcludedLoading.value = true
if (isPublic.value) {
const router = useRouter()
@@ -212,11 +217,14 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
}
} catch (e: any) {
message.error(`${t('msg.error.failedToLoadList')}: ${await extractSdkResponseErrorMsg(e)}`)
+ } finally {
+ isChildrenExcludedLoading.value = false
}
}
const loadChildrenList = async () => {
try {
+ isChildrenLoading.value = true
if (colOptions.value.type === 'bt') return
if (!rowId.value || !column.value) return
if (isPublic.value) {
@@ -262,6 +270,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
}
} catch (e: any) {
message.error(`${t('msg.error.failedToLoadChildrenList')}: ${await extractSdkResponseErrorMsg(e)}`)
+ } finally {
+ isChildrenLoading.value = false
}
}
@@ -357,8 +367,11 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} catch (e: any) {
message.error(`${t('msg.error.unlinkFailed')}: ${await extractSdkResponseErrorMsg(e)}`)
} finally {
- isChildrenExcludedListLoading.value[index] = false
- isChildrenListLoading.value[index] = false
+ // To Keep the Loading State for Minimum 600ms
+ setTimeout(() => {
+ isChildrenExcludedListLoading.value[index] = false
+ isChildrenListLoading.value[index] = false
+ }, 600)
}
reloadData?.(false)
@@ -422,8 +435,12 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} catch (e: any) {
message.error(`Linking failed: ${await extractSdkResponseErrorMsg(e)}`)
} finally {
- isChildrenExcludedListLoading.value[index] = false
- isChildrenListLoading.value[index] = false
+ // To Keep the Loading State for Minimum 600ms
+
+ setTimeout(() => {
+ isChildrenExcludedListLoading.value[index] = false
+ isChildrenListLoading.value[index] = false
+ }, 600)
}
reloadData?.(false)
@@ -466,6 +483,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
isChildrenListLoading,
isChildrenExcludedListLoading,
row,
+ isChildrenLoading,
+ isChildrenExcludedLoading,
deleteRelatedRow,
getRelatedTableRowId,
}
diff --git a/tests/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts b/tests/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts
index 0a95f771af..6ea0a20344 100644
--- a/tests/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts
+++ b/tests/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts
@@ -24,8 +24,7 @@ export class LinkRecord extends BasePage {
{
const childList = linkRecord.getByTestId(`nc-excluded-list-item`);
- const childCards = await childList.count();
- expect(childCards).toEqual(cardTitle.length);
+ expect.poll(() => linkRecord.getByTestId(`nc-excluded-list-item`).count()).toBe(cardTitle.length);
for (let i = 0; i < cardTitle.length; i++) {
await childList.nth(i).locator('.nc-display-value').scrollIntoViewIfNeeded();
await childList.nth(i).locator('.nc-display-value').waitFor({ state: 'visible' });
diff --git a/tests/playwright/pages/Dashboard/common/Cell/index.ts b/tests/playwright/pages/Dashboard/common/Cell/index.ts
index db02bb0f1a..f04090517b 100644
--- a/tests/playwright/pages/Dashboard/common/Cell/index.ts
+++ b/tests/playwright/pages/Dashboard/common/Cell/index.ts
@@ -341,8 +341,7 @@ export class CellPageObject extends BasePage {
await this.rootPage.waitForSelector('.nc-modal-child-list:visible');
// verify child list count & contents
- const childList = this.rootPage.locator('.ant-card:visible');
- expect(await childList.count()).toBe(count);
+ expect.poll(() => this.rootPage.locator('.ant-card:visible').count()).toBe(count);
// close child list
await this.rootPage.locator('.nc-modal-child-list').locator('.nc-close-btn').last().click();
@@ -364,14 +363,21 @@ export class CellPageObject extends BasePage {
// For HM/MM columns
else {
await cell.locator('.nc-datatype-link').click();
+ await this.rootPage
+ .locator(`[data-testid="nc-child-list-item"]`)
+ .last()
+ .waitFor({ state: 'visible', timeout: 3000 });
+
await this.waitForResponse({
uiAction: async () =>
- this.rootPage.locator(`[data-testid="nc-child-list-item"]`).last().click({
- force: true,
- }),
+ await this.rootPage
+ .locator(`[data-testid="nc-child-list-item"]`)
+ .last()
+ .click({ force: true, timeout: 3000 }),
requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
+
await this.rootPage.keyboard.press('Escape');
}
}
diff --git a/tests/playwright/pages/SharedForm/index.ts b/tests/playwright/pages/SharedForm/index.ts
index 756e08319e..cb2ce79ca3 100644
--- a/tests/playwright/pages/SharedForm/index.ts
+++ b/tests/playwright/pages/SharedForm/index.ts
@@ -58,8 +58,7 @@ export class SharedFormPage extends BasePage {
{
const childList = linkRecord.locator(`.ant-card`);
- const childCards = await childList.count();
- expect(childCards).toEqual(cardTitle.length);
+ expect.poll(() => linkRecord.locator(`.ant-card`).count()).toBe(cardTitle.length);
for (let i = 0; i < cardTitle.length; i++) {
expect(await childList.nth(i).textContent()).toContain(cardTitle[i]);
}