Browse Source

Merge pull request #6424 from nocodb/fix/gallery

fix: Links modal pagination
pull/6447/head
Raju Udava 1 year ago committed by GitHub
parent
commit
82faf7806b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui/assets/nc-icons/link.svg
  2. 4
      packages/nc-gui/assets/style.scss
  3. 1
      packages/nc-gui/components.d.ts
  4. 6
      packages/nc-gui/components/virtual-cell/components/Header.vue
  5. 102
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  6. 41
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  7. 99
      packages/nc-gui/components/virtual-cell/components/ListItems.vue
  8. 27
      packages/nc-gui/composables/useLTARStore.ts
  9. 3
      tests/playwright/pages/Dashboard/Grid/Column/LTAR/LinkRecord.ts
  10. 16
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  11. 3
      tests/playwright/pages/SharedForm/index.ts

4
packages/nc-gui/assets/nc-icons/link.svg

@ -0,0 +1,4 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.6665 9.16668C6.95281 9.54943 7.31808 9.86613 7.73754 10.0953C8.157 10.3245 8.62084 10.4608 9.0976 10.4949C9.57437 10.529 10.0529 10.4603 10.5007 10.2932C10.9486 10.1261 11.3552 9.86473 11.6932 9.52668L13.6932 7.52668C14.3004 6.89801 14.6363 6.056 14.6288 5.18201C14.6212 4.30802 14.2706 3.47198 13.6526 2.85395C13.0345 2.23592 12.1985 1.88536 11.3245 1.87777C10.4505 1.87017 9.60851 2.20615 8.97984 2.81335L7.83317 3.95335" stroke="#3366FF" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.33347 7.83332C9.04716 7.45057 8.68189 7.13387 8.26243 6.90469C7.84297 6.67552 7.37913 6.53924 6.90237 6.5051C6.4256 6.47095 5.94707 6.53974 5.49924 6.7068C5.0514 6.87386 4.64472 7.13527 4.3068 7.47332L2.3068 9.47332C1.69961 10.102 1.36363 10.944 1.37122 11.818C1.37881 12.692 1.72938 13.528 2.3474 14.146C2.96543 14.7641 3.80147 15.1146 4.67546 15.1222C5.54945 15.1298 6.39146 14.7938 7.02013 14.1867L8.16013 13.0467" stroke="#3366FF" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

4
packages/nc-gui/assets/style.scss

@ -580,10 +580,6 @@ input[type='number'] {
@apply !block; @apply !block;
} }
.ant-card-body {
@apply !p-2;
}
.ant-pagination .ant-pagination-item-link-icon { .ant-pagination .ant-pagination-item-link-icon {
@apply !block !py-1.5; @apply !block !py-1.5;
} }

1
packages/nc-gui/components.d.ts vendored

@ -159,6 +159,7 @@ declare module '@vue/runtime-core' {
MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default'] MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default']
MiCircleWarning: typeof import('~icons/mi/circle-warning')['default'] MiCircleWarning: typeof import('~icons/mi/circle-warning')['default']
NcIconsInbox: typeof import('~icons/nc-icons/inbox')['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'] PhMagnifyingGlassBold: typeof import('~icons/ph/magnifying-glass-bold')['default']
PhTriangleFill: typeof import('~icons/ph/triangle-fill')['default'] PhTriangleFill: typeof import('~icons/ph/triangle-fill')['default']
RiExternalLinkLine: typeof import('~icons/ri/external-link-line')['default'] RiExternalLinkLine: typeof import('~icons/ri/external-link-line')['default']

6
packages/nc-gui/components/virtual-cell/components/Header.vue

@ -49,9 +49,9 @@ const relationMeta = computed(() => {
<template> <template>
<div class="flex justify-between relative pb-2 items-center"> <div class="flex justify-between relative pb-2 items-center">
<h2 class="text-md font-semibold"> <span class="text-base font-bold flex mt-2 justify-center">
{{ showHeader ? 'Linked Records' : '' }} {{ showHeader ? 'Linked Records' : '' }}
</h2> </span>
<div class="grid grid-cols-[1fr,auto,1fr] justify-center items-center gap-2 absolute inset-0 m-auto"> <div class="grid grid-cols-[1fr,auto,1fr] justify-center items-center gap-2 absolute inset-0 m-auto">
<div class="flex justify-end"> <div class="flex justify-end">
<div class="flex flex-shrink-0 rounded-md gap-1 text-brand-500 items-center bg-gray-100 px-2 py-1"> <div class="flex flex-shrink-0 rounded-md gap-1 text-brand-500 items-center bg-gray-100 px-2 py-1">
@ -108,7 +108,7 @@ const relationMeta = computed(() => {
</div> </div>
</div> </div>
</template> </template>
<InfoIcon class="w-4 h-4" /> <InfoIcon class="w-4 h-4 mt-2" />
</NcTooltip> </NcTooltip>
</div> </div>
</template> </template>

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

@ -2,7 +2,6 @@
import { type ColumnType, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk' import { type ColumnType, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import type { Row } from '#imports' import type { Row } from '#imports'
import InboxIcon from '~icons/nc-icons/inbox' import InboxIcon from '~icons/nc-icons/inbox'
import ColumnIcon from '~icons/nc-icons/column'
import { import {
ColumnInj, ColumnInj,
@ -12,6 +11,7 @@ import {
computed, computed,
inject, inject,
isPrimary, isPrimary,
onKeyStroke,
ref, ref,
useLTARStoreOrThrow, useLTARStoreOrThrow,
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
@ -41,6 +41,7 @@ const {
unlink, unlink,
isChildrenListLoading, isChildrenListLoading,
isChildrenListLinked, isChildrenListLinked,
isChildrenLoading,
relatedTableMeta, relatedTableMeta,
row, row,
link, link,
@ -112,7 +113,13 @@ watch(
) )
watch(expandedFormDlg, () => { watch(expandedFormDlg, () => {
loadChildrenList() if (!expandedFormDlg.value) {
loadChildrenList()
}
})
onKeyStroke('Escape', () => {
vModel.value = false
}) })
</script> </script>
@ -131,6 +138,7 @@ watch(expandedFormDlg, () => {
:relation="relation" :relation="relation"
:linked-records="childrenListCount" :linked-records="childrenListCount"
:table-title="meta?.title" :table-title="meta?.title"
:show-header="true"
:related-table-title="relatedTableMeta?.title" :related-table-title="relatedTableMeta?.title"
:display-value="row.row[displayValueProp]" :display-value="row.row[displayValueProp]"
/> />
@ -152,6 +160,7 @@ watch(expandedFormDlg, () => {
@focus="isFocused = true" @focus="isFocused = true"
@blur="isFocused = false" @blur="isFocused = false"
@keydown.capture.stop @keydown.capture.stop
@change="childrenListPagination.page = 1"
> >
</a-input> </a-input>
</div> </div>
@ -166,28 +175,60 @@ watch(expandedFormDlg, () => {
}" }"
class="overflow-scroll nc-scrollbar-md cursor-pointer pr-1" class="overflow-scroll nc-scrollbar-md cursor-pointer pr-1"
> >
<LazyVirtualCellComponentsListItem <template v-if="isChildrenLoading">
v-for="(refRow, id) in childrenList?.list ?? state?.[colTitle] ?? []" <div
:key="id" v-for="(x, i) in Array.from({ length: 10 })"
:row="refRow" :key="i"
:fields="fields" class="!border-2 flex flex-row gap-2 mb-2 transition-all !rounded-xl relative !border-gray-200 hover:bg-gray-50"
data-testid="nc-child-list-item" >
:attachment="attachmentCol" <a-skeleton-image class="h-24 w-24 !rounded-xl" />
:related-table-display-value-prop="relatedTableDisplayValueProp" <div class="flex flex-col m-[.5rem] gap-2 flex-grow justify-center">
:is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true" <a-skeleton-input class="!w-48 !rounded-xl" active size="small" />
:is-loading="isChildrenListLoading[Number.parseInt(id)]" <div class="flex flex-row gap-6 w-10/12">
@expand="onClick(refRow)" <div class="flex flex-col gap-0.5">
@click=" <a-skeleton-input class="!h-4 !w-12" active size="small" />
() => { <a-skeleton-input class="!h-4 !w-24" active size="small" />
if (isPublic && !isForm) return </div>
isNew <div class="flex flex-col gap-0.5">
? unlinkRow(refRow, Number.parseInt(id)) <a-skeleton-input class="!h-4 !w-12" active size="small" />
: isChildrenListLinked[Number.parseInt(id)] <a-skeleton-input class="!h-4 !w-24" active size="small" />
? unlinkRow(refRow, Number.parseInt(id)) </div>
: linkRow(refRow, Number.parseInt(id)) <div class="flex flex-col gap-0.5">
} <a-skeleton-input class="!h-4 !w-12" active size="small" />
" <a-skeleton-input class="!h-4 !w-24" active size="small" />
/> </div>
<div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" />
</div>
</div>
</div>
</div>
</template>
<template v-else>
<LazyVirtualCellComponentsListItem
v-for="(refRow, id) in childrenList?.list ?? state?.[colTitle] ?? []"
:key="id"
:row="refRow"
:fields="fields"
data-testid="nc-child-list-item"
:attachment="attachmentCol"
:related-table-display-value-prop="relatedTableDisplayValueProp"
:is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true"
:is-loading="isChildrenListLoading[Number.parseInt(id)]"
@expand="onClick(refRow)"
@click="
() => {
if (isPublic && !isForm) return
isNew
? unlinkRow(refRow, Number.parseInt(id))
: isChildrenListLinked[Number.parseInt(id)]
? unlinkRow(refRow, Number.parseInt(id))
: linkRow(refRow, Number.parseInt(id))
}
"
/>
</template>
</div> </div>
</div> </div>
</template> </template>
@ -202,10 +243,7 @@ watch(expandedFormDlg, () => {
<InboxIcon class="w-16 h-16 mx-auto" /> <InboxIcon class="w-16 h-16 mx-auto" />
<p> <p>
No records are linked from table No records are linked from table
<span class="border-gray-300 text-gray-600 rounded-md border-1 p-1"> {{ relatedTableMeta?.title }}
<ColumnIcon class="w-4 h-4 mt-[-2px]" />
{{ relatedTableMeta?.title }}
</span>
</p> </p>
<NcButton <NcButton
v-if="!readonly && childrenListCount < 1" v-if="!readonly && childrenListCount < 1"
@ -219,10 +257,10 @@ watch(expandedFormDlg, () => {
<div class="my-2 bg-gray-50 border-gray-50 border-b-2"></div> <div class="my-2 bg-gray-50 border-gray-50 border-b-2"></div>
<div class="flex flex-row justify-between bg-white relative pt-1"> <div class="flex flex-row justify-between bg-white relative pt-1">
<div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-brand-500 bg-brand-50"> <div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">
{{ childrenListCount || 0 }} records {{ childrenListCount !== 0 ? 'are' : '' }} linked {{ childrenListCount || 0 }} records {{ childrenListCount !== 0 ? 'are' : '' }} linked
</div> </div>
<div v-else class="flex items-center justify-center px-2 rounded-md text-brand-500 bg-brand-50"> <div v-else class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">
{{ state?.[colTitle]?.length || 0 }} records {{ state?.[colTitle]?.length !== 0 ? 'are' : '' }} linked {{ state?.[colTitle]?.length || 0 }} records {{ state?.[colTitle]?.length !== 0 ? 'are' : '' }} linked
</div> </div>
<div class="flex absolute items-center py-2 justify-center w-full"> <div class="flex absolute items-center py-2 justify-center w-full">
@ -238,8 +276,8 @@ watch(expandedFormDlg, () => {
show-less-items show-less-items
/> />
</div> </div>
<div class="flex flex-row gap-1"> <div class="flex flex-row gap-2">
<NcButton v-if="!isForm" type="ghost" class="nc-close-btn" @click="vModel = false"> Cancel </NcButton> <NcButton v-if="!isForm" type="ghost" class="nc-close-btn" @click="vModel = false"> Finish </NcButton>
<NcButton <NcButton
v-if="!readonly && childrenListCount > 0" v-if="!readonly && childrenListCount > 0"
data-testid="nc-child-list-button-link-to" data-testid="nc-child-list-button-link-to"

41
packages/nc-gui/components/virtual-cell/components/ListItem.vue

@ -2,6 +2,7 @@
import { isVirtualCol } from 'nocodb-sdk' import { isVirtualCol } from 'nocodb-sdk'
import { IsFormInj, isImage, useAttachment } from '#imports' import { IsFormInj, isImage, useAttachment } from '#imports'
import MaximizeIcon from '~icons/nc-icons/maximize' import MaximizeIcon from '~icons/nc-icons/maximize'
import LinkIcon from '~icons/nc-icons/link'
const { row, fields, relatedTableDisplayValueProp, isLoading, isLinked, attachment } = defineProps<{ const { row, fields, relatedTableDisplayValueProp, isLoading, isLinked, attachment } = defineProps<{
row: any row: any
@ -46,42 +47,41 @@ const attachments: Attachment[] = computed(() => {
<a-card <a-card
class="!border-1 group transition-all !rounded-xl relative !mb-2 !border-gray-200 hover:bg-gray-50" class="!border-1 group transition-all !rounded-xl relative !mb-2 !border-gray-200 hover:bg-gray-50"
:class="{ :class="{
'!bg-white !border-blue-500': isLoading, '!bg-white': isLoading,
'!border-brand-500 !bg-brand-50 !border-2 !hover:bg-brand-50 !hover:border-brand-500': isLinked, '!border-1': isLinked && !isLoading,
'!hover:border-gray-400': !isLinked, '!hover:border-gray-400': !isLinked,
}" }"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
:hoverable="false" :hoverable="false"
> >
<div class="flex flex-row items-center gap-2 w-full"> <div class="flex flex-row items-center justify-start w-full">
<a-carousel <a-carousel v-if="attachment && attachments && attachments.length" autoplay class="!w-24 !h-24">
v-if="attachment && attachments && attachments.length"
autoplay
class="!w-24 border-1 bg-white border-gray-200 !rounded-md !h-24"
>
<template #customPaging> </template> <template #customPaging> </template>
<template v-for="(attachmen, index) in attachments"> <template v-for="(attachmen, index) in attachments">
<LazyCellAttachmentImage <LazyCellAttachmentImage
v-if="isImage(attachmen.title, attachmen.mimetype ?? attachmen.type)" v-if="isImage(attachmen.title, attachmen.mimetype ?? attachmen.type)"
:key="`carousel-${attachmen.title}-${index}`" :key="`carousel-${attachmen.title}-${index}`"
class="!h-24 !w-24 object-cover !rounded-md" class="!h-24 !w-24 object-cover !rounded-l-xl"
:srcs="getPossibleAttachmentSrc(attachmen)" :srcs="getPossibleAttachmentSrc(attachmen)"
/> />
</template> </template>
</a-carousel> </a-carousel>
<div v-else-if="attachment" class="h-24 w-24 w-full !flex flex-row items-center justify-center"> <div v-else-if="attachment" class="h-24 w-24 w-full !flex flex-row items-center !rounded-l-xl justify-center">
<img class="object-contain h-24 w-24" src="~assets/icons/FileIconImageBox.png" /> <img class="object-contain h-24 w-24" src="~assets/icons/FileIconImageBox.png" />
</div> </div>
<div class="flex flex-col gap-1 flex-grow justify-center"> <div class="flex flex-col m-[.75rem] gap-1 flex-grow justify-center">
<div class="flex justify-between"> <div class="flex justify-between">
<span class="font-bold text-gray-800 ml-1 nc-display-value"> {{ row[relatedTableDisplayValueProp] }} </span> <span class="font-semibold text-gray-800 nc-display-value"> {{ row[relatedTableDisplayValueProp] }} </span>
<MdiCheck <div
v-if="isLinked" v-if="isLinked && !isLoading"
class="text-brand-500 text-0.875"
:class="{ :class="{
'!group-hover:mr-8': fields.length === 0, '!group-hover:mr-12': fields.length === 0,
}" }"
class="w-6 h-6 !text-brand-500" >
/> <LinkIcon class="w-4 h-4" />
Linked
</div>
<MdiLoading <MdiLoading
v-else-if="isLoading" v-else-if="isLoading"
:class="{ :class="{
@ -91,7 +91,7 @@ const attachments: Attachment[] = computed(() => {
/> />
</div> </div>
<div v-if="fields.length > 0 && !isPublic && !isForm" class="flex flex-row gap-4 w-10/12"> <div v-if="fields.length > 0 && !isPublic && !isForm" class="flex ml-[-0.25rem] flex-row gap-4 w-10/12">
<div v-for="field in fields" :key="field.id" :class="attachment ? 'w-1/3' : 'w-1/4'"> <div v-for="field in fields" :key="field.id" :class="attachment ? 'w-1/3' : 'w-1/4'">
<div class="flex flex-col gap-[-1] max-w-72"> <div class="flex flex-col gap-[-1] max-w-72">
<LazySmartsheetHeaderVirtualCell <LazySmartsheetHeaderVirtualCell
@ -121,7 +121,10 @@ const attachments: Attachment[] = computed(() => {
v-if="!isForm && !isPublic" v-if="!isForm && !isPublic"
type="text" type="text"
size="lg" 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)" @click.stop="$emit('expand', row)"
> >
<MaximizeIcon class="w-4 h-4" /> <MaximizeIcon class="w-4 h-4" />

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

@ -2,13 +2,13 @@
import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk' import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import InboxIcon from '~icons/nc-icons/inbox' import InboxIcon from '~icons/nc-icons/inbox'
import ColumnIcon from '~icons/nc-icons/column'
import { import {
ColumnInj, ColumnInj,
IsPublicInj, IsPublicInj,
SaveRowInj, SaveRowInj,
computed, computed,
inject, inject,
onKeyStroke,
ref, ref,
useLTARStoreOrThrow, useLTARStoreOrThrow,
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
@ -30,6 +30,7 @@ const {
isChildrenExcludedListLinked, isChildrenExcludedListLinked,
isChildrenExcludedListLoading, isChildrenExcludedListLoading,
displayValueProp, displayValueProp,
isChildrenExcludedLoading,
childrenListCount, childrenListCount,
loadChildrenExcludedList, loadChildrenExcludedList,
loadChildrenList, loadChildrenList,
@ -138,7 +139,13 @@ const relation = computed(() => {
}) })
watch(expandedFormDlg, () => { watch(expandedFormDlg, () => {
loadChildrenExcludedList(rowState.value) if (!expandedFormDlg.value) {
loadChildrenExcludedList(rowState.value)
}
})
onKeyStroke('Escape', () => {
vModel.value = false
}) })
</script> </script>
@ -176,6 +183,7 @@ watch(expandedFormDlg, () => {
@focus="isFocused = true" @focus="isFocused = true"
@blur="isFocused = false" @blur="isFocused = false"
@keydown.capture.stop @keydown.capture.stop
@change="childrenExcludedListPagination.page = 1"
> >
</a-input> </a-input>
</div> </div>
@ -202,29 +210,61 @@ watch(expandedFormDlg, () => {
<template v-if="childrenExcludedList?.pageInfo?.totalRows"> <template v-if="childrenExcludedList?.pageInfo?.totalRows">
<div class="pb-2 pt-1"> <div class="pb-2 pt-1">
<div class="h-[420px] overflow-scroll nc-scrollbar-md pr-1 cursor-pointer"> <div class="h-[420px] overflow-scroll nc-scrollbar-md pr-1 cursor-pointer">
<LazyVirtualCellComponentsListItem <template v-if="isChildrenExcludedLoading">
v-for="(refRow, id) in childrenExcludedList?.list ?? []" <div
:key="id" v-for="(x, i) in Array.from({ length: 10 })"
data-testid="nc-excluded-list-item" :key="i"
:row="refRow" class="!border-2 flex flex-row gap-2 mb-2 transition-all !rounded-xl relative !border-gray-200 hover:bg-gray-50"
:fields="fields" >
:attachment="attachmentCol" <a-skeleton-image class="h-24 w-24 !rounded-xl" />
:related-table-display-value-prop="relatedTableDisplayValueProp" <div class="flex flex-col m-[.5rem] gap-2 flex-grow justify-center">
:is-loading="isChildrenExcludedListLoading[Number.parseInt(id)]" <a-skeleton-input class="!w-48 !rounded-xl" active size="small" />
:is-linked="isChildrenExcludedListLinked[Number.parseInt(id)]" <div class="flex flex-row gap-6 w-10/12">
@expand=" <div class="flex flex-col gap-0.5">
() => { <a-skeleton-input class="!h-4 !w-12" active size="small" />
expandedFormRow = refRow <a-skeleton-input class="!h-4 !w-24" active size="small" />
expandedFormDlg = true </div>
} <div class="flex flex-col gap-0.5">
" <a-skeleton-input class="!h-4 !w-12" active size="small" />
@click=" <a-skeleton-input class="!h-4 !w-24" active size="small" />
() => { </div>
if (isChildrenExcludedListLinked[Number.parseInt(id)]) unlinkRow(refRow, Number.parseInt(id)) <div class="flex flex-col gap-0.5">
else linkRow(refRow, Number.parseInt(id)) <a-skeleton-input class="!h-4 !w-12" active size="small" />
} <a-skeleton-input class="!h-4 !w-24" active size="small" />
" </div>
/> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" />
</div>
</div>
</div>
</div>
</template>
<template v-else>
<LazyVirtualCellComponentsListItem
v-for="(refRow, id) in childrenExcludedList?.list ?? []"
:key="id"
data-testid="nc-excluded-list-item"
:row="refRow"
:fields="fields"
:attachment="attachmentCol"
:related-table-display-value-prop="relatedTableDisplayValueProp"
:is-loading="isChildrenExcludedListLoading[Number.parseInt(id)]"
:is-linked="isChildrenExcludedListLinked[Number.parseInt(id)]"
@expand="
() => {
expandedFormRow = refRow
expandedFormDlg = true
}
"
@click="
() => {
if (isChildrenExcludedListLinked[Number.parseInt(id)]) unlinkRow(refRow, Number.parseInt(id))
else linkRow(refRow, Number.parseInt(id))
}
"
/>
</template>
</div> </div>
</div> </div>
</template> </template>
@ -232,16 +272,13 @@ watch(expandedFormDlg, () => {
<InboxIcon class="w-16 h-16 mx-auto" /> <InboxIcon class="w-16 h-16 mx-auto" />
<p> <p>
There are no records in table There are no records in table
<span class="border-gray-300 text-gray-600 rounded-md border-1 p-1"> {{ relatedTableMeta?.title }}
<ColumnIcon class="w-4 h-4 mt-[-2px]" />
{{ relatedTableMeta?.title }}
</span>
</p> </p>
</div> </div>
<div class="my-2 bg-gray-50 border-gray-50 border-b-2"></div> <div class="my-2 bg-gray-50 border-gray-50 border-b-2"></div>
<div class="flex flex-row justify-between bg-white relative pt-1"> <div class="flex flex-row justify-between bg-white relative pt-1">
<div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-brand-500 bg-brand-50"> <div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">
{{ relation === 'bt' ? (row.row[relatedTableMeta?.title] ? '1' : 0) : childrenListCount ?? 'No' }} records {{ relation === 'bt' ? (row.row[relatedTableMeta?.title] ? '1' : 0) : childrenListCount ?? 'No' }} records
{{ childrenListCount !== 0 ? 'are' : '' }} linked {{ childrenListCount !== 0 ? 'are' : '' }} linked
</div> </div>
@ -258,7 +295,7 @@ watch(expandedFormDlg, () => {
show-less-items show-less-items
/> />
</div> </div>
<NcButton class="nc-close-btn ml-auto" type="ghost" @click="vModel = false"> Cancel </NcButton> <NcButton class="nc-close-btn ml-auto" type="ghost" @click="vModel = false"> Finish </NcButton>
</div> </div>
<Suspense> <Suspense>
<LazySmartsheetExpandedForm <LazySmartsheetExpandedForm

27
packages/nc-gui/composables/useLTARStore.ts

@ -64,12 +64,16 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
size: 10, size: 10,
}) })
const isChildrenLoading = ref(false)
const isChildrenListLoading = ref<Array<boolean>>([]) const isChildrenListLoading = ref<Array<boolean>>([])
const isChildrenListLinked = ref<Array<boolean>>([]) const isChildrenListLinked = ref<Array<boolean>>([])
const isChildrenExcludedListLoading = ref<Array<boolean>>([]) const isChildrenExcludedListLoading = ref<Array<boolean>>([])
const isChildrenExcludedLoading = ref(false)
const isChildrenExcludedListLinked = ref<Array<boolean>>([]) const isChildrenExcludedListLinked = ref<Array<boolean>>([])
const newRowState = reactive({ const newRowState = reactive({
@ -127,6 +131,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
const loadChildrenExcludedList = async (activeState?: any) => { const loadChildrenExcludedList = async (activeState?: any) => {
if (activeState) newRowState.state = activeState if (activeState) newRowState.state = activeState
try { try {
isChildrenExcludedLoading.value = true
if (isPublic.value) { if (isPublic.value) {
const router = useRouter() const router = useRouter()
@ -212,11 +217,14 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} }
} catch (e: any) { } catch (e: any) {
message.error(`${t('msg.error.failedToLoadList')}: ${await extractSdkResponseErrorMsg(e)}`) message.error(`${t('msg.error.failedToLoadList')}: ${await extractSdkResponseErrorMsg(e)}`)
} finally {
isChildrenExcludedLoading.value = false
} }
} }
const loadChildrenList = async () => { const loadChildrenList = async () => {
try { try {
isChildrenLoading.value = true
if (colOptions.value.type === 'bt') return if (colOptions.value.type === 'bt') return
if (!rowId.value || !column.value) return if (!rowId.value || !column.value) return
if (isPublic.value) { if (isPublic.value) {
@ -262,6 +270,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} }
} catch (e: any) { } catch (e: any) {
message.error(`${t('msg.error.failedToLoadChildrenList')}: ${await extractSdkResponseErrorMsg(e)}`) message.error(`${t('msg.error.failedToLoadChildrenList')}: ${await extractSdkResponseErrorMsg(e)}`)
} finally {
isChildrenLoading.value = false
} }
} }
@ -357,8 +367,11 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} catch (e: any) { } catch (e: any) {
message.error(`${t('msg.error.unlinkFailed')}: ${await extractSdkResponseErrorMsg(e)}`) message.error(`${t('msg.error.unlinkFailed')}: ${await extractSdkResponseErrorMsg(e)}`)
} finally { } finally {
isChildrenExcludedListLoading.value[index] = false // To Keep the Loading State for Minimum 600ms
isChildrenListLoading.value[index] = false setTimeout(() => {
isChildrenExcludedListLoading.value[index] = false
isChildrenListLoading.value[index] = false
}, 600)
} }
reloadData?.(false) reloadData?.(false)
@ -422,8 +435,12 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} catch (e: any) { } catch (e: any) {
message.error(`Linking failed: ${await extractSdkResponseErrorMsg(e)}`) message.error(`Linking failed: ${await extractSdkResponseErrorMsg(e)}`)
} finally { } finally {
isChildrenExcludedListLoading.value[index] = false // To Keep the Loading State for Minimum 600ms
isChildrenListLoading.value[index] = false
setTimeout(() => {
isChildrenExcludedListLoading.value[index] = false
isChildrenListLoading.value[index] = false
}, 600)
} }
reloadData?.(false) reloadData?.(false)
@ -466,6 +483,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
isChildrenListLoading, isChildrenListLoading,
isChildrenExcludedListLoading, isChildrenExcludedListLoading,
row, row,
isChildrenLoading,
isChildrenExcludedLoading,
deleteRelatedRow, deleteRelatedRow,
getRelatedTableRowId, getRelatedTableRowId,
} }

3
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 childList = linkRecord.getByTestId(`nc-excluded-list-item`);
const childCards = await childList.count(); expect.poll(() => linkRecord.getByTestId(`nc-excluded-list-item`).count()).toBe(cardTitle.length);
expect(childCards).toEqual(cardTitle.length);
for (let i = 0; i < cardTitle.length; i++) { 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').scrollIntoViewIfNeeded();
await childList.nth(i).locator('.nc-display-value').waitFor({ state: 'visible' }); await childList.nth(i).locator('.nc-display-value').waitFor({ state: 'visible' });

16
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'); await this.rootPage.waitForSelector('.nc-modal-child-list:visible');
// verify child list count & contents // verify child list count & contents
const childList = this.rootPage.locator('.ant-card:visible'); expect.poll(() => this.rootPage.locator('.ant-card:visible').count()).toBe(count);
expect(await childList.count()).toBe(count);
// close child list // close child list
await this.rootPage.locator('.nc-modal-child-list').locator('.nc-close-btn').last().click(); 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 // For HM/MM columns
else { else {
await cell.locator('.nc-datatype-link').click(); 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({ await this.waitForResponse({
uiAction: async () => uiAction: async () =>
this.rootPage.locator(`[data-testid="nc-child-list-item"]`).last().click({ await this.rootPage
force: true, .locator(`[data-testid="nc-child-list-item"]`)
}), .last()
.click({ force: true, timeout: 3000 }),
requestUrlPathToMatch: '/api/v1/db/data/noco/', requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'], httpMethodsToMatch: ['GET'],
}); });
await this.rootPage.keyboard.press('Escape'); await this.rootPage.keyboard.press('Escape');
} }
} }

3
tests/playwright/pages/SharedForm/index.ts

@ -58,8 +58,7 @@ export class SharedFormPage extends BasePage {
{ {
const childList = linkRecord.locator(`.ant-card`); const childList = linkRecord.locator(`.ant-card`);
const childCards = await childList.count(); expect.poll(() => linkRecord.locator(`.ant-card`).count()).toBe(cardTitle.length);
expect(childCards).toEqual(cardTitle.length);
for (let i = 0; i < cardTitle.length; i++) { for (let i = 0; i < cardTitle.length; i++) {
expect(await childList.nth(i).textContent()).toContain(cardTitle[i]); expect(await childList.nth(i).textContent()).toContain(cardTitle[i]);
} }

Loading…
Cancel
Save