Browse Source

fix(nc-gui): Update offset of LinkedRecords in accordance with links and unlinks (#7841)

* fix(nc-gui): update links

* fix(nc-gui): update offset

* fix(nc-gui): offset reset

* fix: offset issue

* fix: loading state not working

* fix: tests

* fix: failign tests

* fix: update offset if same or greater than count
pull/7672/head
Anbarasu 8 months ago committed by GitHub
parent
commit
031d261a1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  2. 4
      packages/nc-gui/components/virtual-cell/HasMany.vue
  3. 6
      packages/nc-gui/components/virtual-cell/Links.vue
  4. 4
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  5. 73
      packages/nc-gui/components/virtual-cell/components/LinkedItems.vue
  6. 3
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  7. 80
      packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue
  8. 61
      packages/nc-gui/composables/useLTARStore.ts
  9. 2
      tests/playwright/pages/Dashboard/ExpandedForm/index.ts
  10. 1
      tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts

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

@ -121,7 +121,7 @@ watch([listItemsDlg], () => {
/> />
</div> </div>
<LazyVirtualCellComponentsListItems <LazyVirtualCellComponentsUnLinkedItems
v-if="listItemsDlg" v-if="listItemsDlg"
v-model="listItemsDlg" v-model="listItemsDlg"
:column="belongsToColumn" :column="belongsToColumn"

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

@ -132,9 +132,9 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
/> />
</div> </div>
<LazyVirtualCellComponentsListItems v-if="listItemsDlg || childListDlg" v-model="listItemsDlg" :column="hasManyColumn" /> <LazyVirtualCellComponentsUnLinkedItems v-if="listItemsDlg || childListDlg" v-model="listItemsDlg" :column="hasManyColumn" />
<LazyVirtualCellComponentsListChildItems <LazyVirtualCellComponentsLinkedItems
v-if="listItemsDlg || childListDlg" v-if="listItemsDlg || childListDlg"
v-model="childListDlg" v-model="childListDlg"
:cell-value="localCellValue" :cell-value="localCellValue"

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

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from '@vue/reactivity' import { computed } from '@vue/reactivity'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { ref } from 'vue'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { ref } from 'vue'
import { ActiveCellInj, CellValueInj, ColumnInj, IsUnderLookupInj, inject, useSelectedCellKeyupListener } from '#imports' import { ActiveCellInj, CellValueInj, ColumnInj, IsUnderLookupInj, inject, useSelectedCellKeyupListener } from '#imports'
const value = inject(CellValueInj, ref(0)) const value = inject(CellValueInj, ref(0))
@ -151,13 +151,13 @@ watch([listItemsDlg], () => {
@click.stop="openListDlg" @click.stop="openListDlg"
/> />
</div> </div>
<LazyVirtualCellComponentsListItems <LazyVirtualCellComponentsUnLinkedItems
v-if="listItemsDlg || childListDlg" v-if="listItemsDlg || childListDlg"
v-model="listItemsDlg" v-model="listItemsDlg"
:column="relatedTableDisplayColumn" :column="relatedTableDisplayColumn"
/> />
<LazyVirtualCellComponentsListChildItems <LazyVirtualCellComponentsLinkedItems
v-if="listItemsDlg || childListDlg" v-if="listItemsDlg || childListDlg"
v-model="childListDlg" v-model="childListDlg"
:items="toatlRecordsLinked" :items="toatlRecordsLinked"

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

@ -133,9 +133,9 @@ const m2mColumn = computed(
/> />
</div> </div>
<LazyVirtualCellComponentsListItems v-if="listItemsDlg || childListDlg" v-model="listItemsDlg" :column="m2mColumn" /> <LazyVirtualCellComponentsUnLinkedItems v-if="listItemsDlg || childListDlg" v-model="listItemsDlg" :column="m2mColumn" />
<LazyVirtualCellComponentsListChildItems <LazyVirtualCellComponentsLinkedItems
v-if="listItemsDlg || childListDlg" v-if="listItemsDlg || childListDlg"
v-model="childListDlg" v-model="childListDlg"
:cell-value="localCellValue" :cell-value="localCellValue"

73
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue → packages/nc-gui/components/virtual-cell/components/LinkedItems.vue

@ -47,6 +47,8 @@ const {
childrenListCount, childrenListCount,
loadChildrenList, loadChildrenList,
childrenListPagination, childrenListPagination,
childrenExcludedOffsetCount,
childrenListOffsetCount,
relatedTableDisplayValueProp, relatedTableDisplayValueProp,
displayValueTypeAndFormatProp, displayValueTypeAndFormatProp,
unlink, unlink,
@ -121,9 +123,8 @@ watch(
) )
watch(expandedFormDlg, () => { watch(expandedFormDlg, () => {
if (!expandedFormDlg.value) { childrenExcludedOffsetCount.value = 0
loadChildrenList() childrenListOffsetCount.value = 0
}
}) })
/* /*
@ -199,6 +200,12 @@ onMounted(() => {
window.addEventListener('keydown', linkedShortcuts) window.addEventListener('keydown', linkedShortcuts)
}) })
const childrenListRef = ref<HTMLDivElement>()
watch(childrenListPagination, () => {
childrenListRef.value?.scrollTo({ top: 0, behavior: 'smooth' })
})
onUnmounted(() => { onUnmounted(() => {
childrenListPagination.query = '' childrenListPagination.query = ''
window.removeEventListener('keydown', linkedShortcuts) window.removeEventListener('keydown', linkedShortcuts)
@ -208,22 +215,22 @@ onUnmounted(() => {
<template> <template>
<NcModal <NcModal
v-model:visible="vModel" v-model:visible="vModel"
:body-style="{ 'max-height': '640px', 'height': '85vh' }"
:class="{ active: vModel }" :class="{ active: vModel }"
:footer="null"
:closable="false" :closable="false"
size="medium" :footer="null"
:width="isForm ? 600 : 800" :width="isForm ? 600 : 800"
:body-style="{ 'max-height': '640px', 'height': '85vh' }" size="medium"
wrap-class-name="nc-modal-child-list" wrap-class-name="nc-modal-child-list"
> >
<LazyVirtualCellComponentsHeader <LazyVirtualCellComponentsHeader
v-if="!isForm" v-if="!isForm"
:relation="relation" :display-value="headerDisplayValue"
:linked-records="childrenListCount"
:table-title="meta?.title"
:header="$t('activity.linkedRecords')" :header="$t('activity.linkedRecords')"
:linked-records="childrenListCount"
:related-table-title="relatedTableMeta?.title" :related-table-title="relatedTableMeta?.title"
:display-value="headerDisplayValue" :relation="relation"
:table-title="meta?.title"
/> />
<div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2"> <div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2">
<div class="flex items-center border-1 p-1 rounded-md w-full border-gray-200 !focus-within:border-primary"> <div class="flex items-center border-1 p-1 rounded-md w-full border-gray-200 !focus-within:border-primary">
@ -231,10 +238,11 @@ onUnmounted(() => {
<a-input <a-input
ref="filterQueryRef" ref="filterQueryRef"
v-model:value="childrenListPagination.query" v-model:value="childrenListPagination.query"
:bordered="false"
:placeholder="`Search in ${relatedTableMeta?.title}`" :placeholder="`Search in ${relatedTableMeta?.title}`"
class="w-full !sm:rounded-md xs:min-h-8 !xs:rounded-xl" class="w-full !sm:rounded-md xs:min-h-8 !xs:rounded-xl"
size="small" size="small"
:bordered="false" @change="childrenListPagination.page = 1"
@keydown.capture.stop=" @keydown.capture.stop="
(e) => { (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
@ -242,39 +250,38 @@ onUnmounted(() => {
} }
} }
" "
@change="childrenListPagination.page = 1"
> >
</a-input> </a-input>
</div> </div>
</div> </div>
<div class="flex flex-col flex-grow nc-scrollbar-md cursor-pointer pr-1"> <div ref="childrenListRef" class="flex flex-col flex-grow nc-scrollbar-md cursor-pointer pr-1">
<div v-if="isDataExist || isChildrenLoading" class="mt-2 mb-2"> <div v-if="isDataExist || isChildrenLoading" class="mt-2 mb-2">
<div class="cursor-pointer pr-1"> <div class="cursor-pointer pr-1">
<template v-if="isChildrenLoading"> <template v-if="isChildrenLoading">
<div <div
v-for="(x, i) in Array.from({ length: skeletonCount })" v-for="(_x, i) in Array.from({ length: skeletonCount })"
:key="i" :key="i"
class="!border-2 flex flex-row gap-2 mb-2 transition-all !rounded-xl relative !border-gray-200 hover:bg-gray-50" class="!border-2 flex flex-row gap-2 mb-2 transition-all !rounded-xl relative !border-gray-200 hover:bg-gray-50"
> >
<a-skeleton-image class="h-24 w-24 !rounded-xl" /> <a-skeleton-image class="h-24 w-24 !rounded-xl" />
<div class="flex flex-col m-[.5rem] gap-2 flex-grow justify-center"> <div class="flex flex-col m-[.5rem] gap-2 flex-grow justify-center">
<a-skeleton-input class="!w-48 !rounded-xl" active size="small" /> <a-skeleton-input active class="!w-48 !rounded-xl" size="small" />
<div class="flex flex-row gap-6 w-10/12"> <div class="flex flex-row gap-6 w-10/12">
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" /> <a-skeleton-input active class="!h-4 !w-24" size="small" />
</div> </div>
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" /> <a-skeleton-input active class="!h-4 !w-24" size="small" />
</div> </div>
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" /> <a-skeleton-input active class="!h-4 !w-24" size="small" />
</div> </div>
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" /> <a-skeleton-input active class="!h-4 !w-24" size="small" />
</div> </div>
</div> </div>
</div> </div>
@ -284,24 +291,28 @@ onUnmounted(() => {
<LazyVirtualCellComponentsListItem <LazyVirtualCellComponentsListItem
v-for="(refRow, id) in childrenList?.list ?? state?.[colTitle] ?? []" v-for="(refRow, id) in childrenList?.list ?? state?.[colTitle] ?? []"
:key="id" :key="id"
:row="refRow"
:fields="fields"
data-testid="nc-child-list-item"
:attachment="attachmentCol" :attachment="attachmentCol"
:related-table-display-value-prop="relatedTableDisplayValueProp"
:display-value-type-and-format-prop="displayValueTypeAndFormatProp" :display-value-type-and-format-prop="displayValueTypeAndFormatProp"
:fields="fields"
:is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true" :is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true"
:is-loading="isChildrenListLoading[Number.parseInt(id)]" :is-loading="isChildrenListLoading[Number.parseInt(id)]"
:related-table-display-value-prop="relatedTableDisplayValueProp"
:row="refRow"
data-testid="nc-child-list-item"
@click="linkOrUnLink(refRow, id)"
@expand="onClick(refRow)" @expand="onClick(refRow)"
@keydown.space.prevent="linkOrUnLink(refRow, id)" @keydown.space.prevent="linkOrUnLink(refRow, id)"
@keydown.enter.prevent="() => onClick(refRow, id)" @keydown.enter.prevent="() => onClick(refRow, id)"
@click="linkOrUnLink(refRow, id)"
/> />
</template> </template>
</div> </div>
</div> </div>
<div v-else class="pt-1 flex flex-col gap-4 my-auto items-center justify-center text-gray-500 text-center"> <div v-else class="pt-1 flex flex-col gap-4 my-auto items-center justify-center text-gray-500 text-center">
<img src="~assets/img/placeholder/link-records.png" class="!w-[18.5rem] flex-none" /> <img
:alt="$t('msg.clickLinkRecordsToAddLinkFromTable', { tableName: relatedTableMeta?.title })"
class="!w-[18.5rem] flex-none"
src="~assets/img/placeholder/link-records.png"
/>
<div class="text-2xl text-gray-700 font-bold">{{ $t('msg.noLinkedRecords') }}</div> <div class="text-2xl text-gray-700 font-bold">{{ $t('msg.noLinkedRecords') }}</div>
<div class="text-gray-700"> <div class="text-gray-700">
{{ $t('msg.clickLinkRecordsToAddLinkFromTable', { tableName: relatedTableMeta?.title }) }} {{ $t('msg.clickLinkRecordsToAddLinkFromTable', { tableName: relatedTableMeta?.title }) }}
@ -352,7 +363,7 @@ onUnmounted(() => {
/> />
</div> </div>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<NcButton v-if="!isForm" type="ghost" class="nc-close-btn" @click="vModel = false"> {{ $t('general.finish') }} </NcButton> <NcButton v-if="!isForm" class="nc-close-btn" type="ghost" @click="vModel = false"> {{ $t('general.finish') }} </NcButton>
<NcButton <NcButton
v-if="!readOnly && childrenListCount > 0" v-if="!readOnly && childrenListCount > 0"
v-e="['c:links:link']" v-e="['c:links:link']"
@ -388,7 +399,7 @@ onUnmounted(() => {
</NcModal> </NcModal>
</template> </template>
<style scoped lang="scss"> <style lang="scss" scoped>
:deep(.nc-nested-list-item .ant-card-body) { :deep(.nc-nested-list-item .ant-card-body) {
@apply !px-1 !py-0; @apply !px-1 !py-0;
} }

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

@ -94,7 +94,6 @@ const displayValue = computed(() => {
:class="{ :class="{
'!bg-white': isLoading, '!bg-white': isLoading,
'!border-1': isLinked && !isLoading, '!border-1': isLinked && !isLoading,
'!hover:border-gray-400': !isLinked,
'!cursor-auto !hover:bg-white': readOnly, '!cursor-auto !hover:bg-white': readOnly,
}" }"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
@ -179,7 +178,7 @@ const displayValue = computed(() => {
v-if="!isForm && !isPublic && !readOnly" v-if="!isForm && !isPublic && !readOnly"
v-e="['c:row-expand:open']" v-e="['c:row-expand:open']"
type="text" type="text"
size="lg" size="medium"
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="!px-2 nc-expand-item !group-hover:block !hidden !border-1 !shadow-sm !border-gray-200 !bg-white !absolute right-3 bottom-3"
:class="{ :class="{
'!group-hover:right-1.8 !group-hover:bottom-1.7': fields.length === 0, '!group-hover:right-1.8 !group-hover:bottom-1.7': fields.length === 0,

80
packages/nc-gui/components/virtual-cell/components/ListItems.vue → packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue

@ -35,6 +35,8 @@ const { $e } = useNuxtApp()
const { const {
childrenExcludedList, childrenExcludedList,
isChildrenExcludedListLinked, isChildrenExcludedListLinked,
childrenExcludedOffsetCount,
childrenListOffsetCount,
isChildrenExcludedListLoading, isChildrenExcludedListLoading,
isChildrenExcludedLoading, isChildrenExcludedLoading,
childrenListCount, childrenListCount,
@ -166,6 +168,8 @@ watch(expandedFormDlg, () => {
} }
loadChildrenExcludedList(rowState.value) loadChildrenExcludedList(rowState.value)
} }
childrenExcludedOffsetCount.value = 0
childrenListOffsetCount.value = 0
}) })
watch(filterQueryRef, () => { watch(filterQueryRef, () => {
@ -237,6 +241,12 @@ const linkedShortcuts = (e: KeyboardEvent) => {
} }
} }
const childrenExcludedListRef = ref<HTMLDivElement>()
watch(childrenExcludedListPagination, () => {
childrenExcludedListRef.value?.scrollTo({ top: 0, behavior: 'smooth' })
})
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', linkedShortcuts) window.addEventListener('keydown', linkedShortcuts)
}) })
@ -250,20 +260,20 @@ onUnmounted(() => {
<template> <template>
<NcModal <NcModal
v-model:visible="vModel" v-model:visible="vModel"
:body-style="{ 'max-height': '640px', 'height': '85vh' }"
:class="{ active: vModel }" :class="{ active: vModel }"
:closable="false"
:footer="null" :footer="null"
:width="isForm ? 600 : 800" :width="isForm ? 600 : 800"
:closable="false"
:body-style="{ 'max-height': '640px', 'height': '85vh' }"
wrap-class-name="nc-modal-link-record" wrap-class-name="nc-modal-link-record"
> >
<LazyVirtualCellComponentsHeader <LazyVirtualCellComponentsHeader
v-if="!isForm" v-if="!isForm"
:relation="relation"
:table-title="meta?.title"
:related-table-title="relatedTableMeta?.title"
:display-value="headerDisplayValue" :display-value="headerDisplayValue"
:header="$t('activity.addNewLink')" :header="$t('activity.addNewLink')"
:related-table-title="relatedTableMeta?.title"
:relation="relation"
:table-title="meta?.title"
/> />
<div class="flex mt-2 mb-2 items-center gap-2"> <div class="flex mt-2 mb-2 items-center gap-2">
<div class="flex items-center border-1 p-1 rounded-md w-full border-gray-200 !focus-within:border-primary"> <div class="flex items-center border-1 p-1 rounded-md w-full border-gray-200 !focus-within:border-primary">
@ -271,10 +281,11 @@ onUnmounted(() => {
<a-input <a-input
ref="filterQueryRef" ref="filterQueryRef"
v-model:value="childrenExcludedListPagination.query" v-model:value="childrenExcludedListPagination.query"
:bordered="false"
:placeholder="`${$t('general.searchIn')} ${relatedTableMeta?.title}`" :placeholder="`${$t('general.searchIn')} ${relatedTableMeta?.title}`"
class="w-full !rounded-md nc-excluded-search xs:min-h-8" class="w-full !rounded-md nc-excluded-search xs:min-h-8"
size="small" size="small"
:bordered="false" @change="childrenExcludedListPagination.page = 1"
@keydown.capture.stop=" @keydown.capture.stop="
(e) => { (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
@ -282,7 +293,6 @@ onUnmounted(() => {
} }
} }
" "
@change="childrenExcludedListPagination.page = 1"
> >
</a-input> </a-input>
</div> </div>
@ -293,9 +303,9 @@ onUnmounted(() => {
<NcButton <NcButton
v-if="!isPublic" v-if="!isPublic"
v-e="['c:row-expand:open']" v-e="['c:row-expand:open']"
type="secondary"
:size="isMobileMode ? 'medium' : 'small'" :size="isMobileMode ? 'medium' : 'small'"
class="!text-brand-500" class="!text-brand-500"
type="secondary"
@click="addNewRecord" @click="addNewRecord"
> >
<div class="flex items-center gap-1 px-4"><MdiPlus v-if="!isMobileMode" /> {{ $t('activity.newRecord') }}</div> <div class="flex items-center gap-1 px-4"><MdiPlus v-if="!isMobileMode" /> {{ $t('activity.newRecord') }}</div>
@ -303,32 +313,32 @@ onUnmounted(() => {
</div> </div>
<template v-if="childrenExcludedList?.pageInfo?.totalRows"> <template v-if="childrenExcludedList?.pageInfo?.totalRows">
<div class="overflow-scroll nc-scrollbar-md pr-1 cursor-pointer flex flex-col flex-grow"> <div ref="childrenExcludedListRef" class="overflow-scroll nc-scrollbar-md pr-1 cursor-pointer flex flex-col flex-grow">
<template v-if="isChildrenExcludedLoading"> <template v-if="isChildrenExcludedLoading">
<div <div
v-for="(x, i) in Array.from({ length: 10 })" v-for="(_x, i) in Array.from({ length: 10 })"
:key="i" :key="i"
class="!border-2 flex flex-row gap-2 mb-2 transition-all !rounded-xl relative !border-gray-200 hover:bg-gray-50" class="!border-2 flex flex-row gap-2 mb-2 transition-all !rounded-xl relative !border-gray-200 hover:bg-gray-50"
> >
<a-skeleton-image class="h-24 w-24 !rounded-xl" /> <a-skeleton-image class="h-24 w-24 !rounded-xl" />
<div class="flex flex-col m-[.5rem] gap-2 flex-grow justify-center"> <div class="flex flex-col m-[.5rem] gap-2 flex-grow justify-center">
<a-skeleton-input class="!xs:w-30 !w-48 !rounded-xl" active size="small" /> <a-skeleton-input active class="!xs:w-30 !w-48 !rounded-xl" size="small" />
<div class="flex flex-row gap-6 w-10/12"> <div class="flex flex-row gap-6 w-10/12">
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!xs:hidden !h-4 !w-24" active size="small" /> <a-skeleton-input active class="!xs:hidden !h-4 !w-24" size="small" />
</div> </div>
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!xs:hidden !h-4 !w-24" active size="small" /> <a-skeleton-input active class="!xs:hidden !h-4 !w-24" size="small" />
</div> </div>
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!xs:hidden !h-4 !w-24" active size="small" /> <a-skeleton-input active class="!xs:hidden !h-4 !w-24" size="small" />
</div> </div>
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input active class="!h-4 !w-12" size="small" />
<a-skeleton-input class="!xs:hidden !h-4 !w-24" active size="small" /> <a-skeleton-input active class="!xs:hidden !h-4 !w-24" size="small" />
</div> </div>
</div> </div>
</div> </div>
@ -338,14 +348,15 @@ onUnmounted(() => {
<LazyVirtualCellComponentsListItem <LazyVirtualCellComponentsListItem
v-for="(refRow, id) in childrenExcludedList?.list ?? []" v-for="(refRow, id) in childrenExcludedList?.list ?? []"
:key="id" :key="id"
data-testid="nc-excluded-list-item"
:row="refRow"
:fields="fields"
:attachment="attachmentCol" :attachment="attachmentCol"
:related-table-display-value-prop="relatedTableDisplayValueProp"
:display-value-type-and-format-prop="displayValueTypeAndFormatProp" :display-value-type-and-format-prop="displayValueTypeAndFormatProp"
:is-loading="isChildrenExcludedListLoading[Number.parseInt(id)]" :fields="fields"
:is-linked="isChildrenExcludedListLinked[Number.parseInt(id)]" :is-linked="isChildrenExcludedListLinked[Number.parseInt(id)]"
:is-loading="isChildrenExcludedListLoading[Number.parseInt(id)]"
:related-table-display-value-prop="relatedTableDisplayValueProp"
:row="refRow"
data-testid="nc-excluded-list-item"
@click="() => onClick(refRow, id)"
@expand=" @expand="
() => { () => {
expandedFormRow = refRow expandedFormRow = refRow
@ -354,7 +365,6 @@ onUnmounted(() => {
" "
@keydown.space.prevent="() => onClick(refRow, id)" @keydown.space.prevent="() => onClick(refRow, id)"
@keydown.enter.prevent="() => onClick(refRow, id)" @keydown.enter.prevent="() => onClick(refRow, id)"
@click="() => onClick(refRow, id)"
/> />
</template> </template>
</div> </div>
@ -372,7 +382,7 @@ onUnmounted(() => {
v-if="childrenExcludedList?.pageInfo" v-if="childrenExcludedList?.pageInfo"
v-model:current="childrenExcludedListPagination.page" v-model:current="childrenExcludedListPagination.page"
v-model:page-size="childrenExcludedListPagination.size" v-model:page-size="childrenExcludedListPagination.size"
:total="+childrenExcludedList.pageInfo.totalRows" :total="+childrenExcludedList?.pageInfo?.totalRows"
entity-name="links-excluded-list" entity-name="links-excluded-list"
/> />
</div> </div>
@ -390,7 +400,7 @@ onUnmounted(() => {
v-if="childrenExcludedList?.pageInfo" v-if="childrenExcludedList?.pageInfo"
v-model:current="childrenExcludedListPagination.page" v-model:current="childrenExcludedListPagination.page"
v-model:page-size="childrenExcludedListPagination.size" v-model:page-size="childrenExcludedListPagination.size"
:total="+childrenExcludedList.pageInfo.totalRows" :total="+childrenExcludedList?.pageInfo?.totalRows"
entity-name="links-excluded-list" entity-name="links-excluded-list"
mode="simple" mode="simple"
/> />
@ -401,7 +411,15 @@ onUnmounted(() => {
<LazySmartsheetExpandedForm <LazySmartsheetExpandedForm
v-if="expandedFormDlg" v-if="expandedFormDlg"
v-model="expandedFormDlg" v-model="expandedFormDlg"
:close-after-save="isExpandedFormCloseAfterSave"
:meta="relatedTableMeta" :meta="relatedTableMeta"
:new-record-header="
isExpandedFormCloseAfterSave
? $t('activity.tableNameCreateNewRecord', {
tableName: relatedTableMeta?.title,
})
: undefined
"
:row="{ :row="{
row: expandedFormRow, row: expandedFormRow,
oldRow: {}, oldRow: {},
@ -415,14 +433,6 @@ onUnmounted(() => {
:row-id="extractPkFromRow(expandedFormRow, relatedTableMeta.columns as ColumnType[])" :row-id="extractPkFromRow(expandedFormRow, relatedTableMeta.columns as ColumnType[])"
:state="newRowState" :state="newRowState"
use-meta-fields use-meta-fields
:close-after-save="isExpandedFormCloseAfterSave"
:new-record-header="
isExpandedFormCloseAfterSave
? $t('activity.tableNameCreateNewRecord', {
tableName: relatedTableMeta?.title,
})
: undefined
"
@created-record="onCreatedRecord" @created-record="onCreatedRecord"
/> />
</Suspense> </Suspense>

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

@ -1,4 +1,3 @@
import { UITypes, dateFormats, parseStringDateTime, timeFormats } from 'nocodb-sdk'
import type { import type {
type ColumnType, type ColumnType,
type LinkToAnotherRecordType, type LinkToAnotherRecordType,
@ -7,7 +6,9 @@ import type {
type RequestParams, type RequestParams,
type TableType, type TableType,
} from 'nocodb-sdk' } from 'nocodb-sdk'
import { UITypes, dateFormats, parseStringDateTime, timeFormats } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import type { Row } from '#imports'
import { import {
IsPublicInj, IsPublicInj,
Modal, Modal,
@ -30,7 +31,6 @@ import {
useSharedView, useSharedView,
watch, watch,
} from '#imports' } from '#imports'
import type { Row } from '#imports'
interface DataApiResponse { interface DataApiResponse {
list: Record<string, any> list: Record<string, any>
@ -43,7 +43,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
column: Ref<Required<ColumnType>>, column: Ref<Required<ColumnType>>,
row: Ref<Row>, row: Ref<Row>,
isNewRow: ComputedRef<boolean> | Ref<boolean>, isNewRow: ComputedRef<boolean> | Ref<boolean>,
reloadData = (_params: { shouldShowLoading?: boolean }) => {}, _reloadData = (_params: { shouldShowLoading?: boolean }) => {},
) => { ) => {
// state // state
const { metas, getMeta } = useMetas() const { metas, getMeta } = useMetas()
@ -66,6 +66,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
query: '', query: '',
size: 10, size: 10,
}) })
const childrenExcludedOffsetCount = ref(0)
const childrenListPagination = reactive({ const childrenListPagination = reactive({
page: 1, page: 1,
@ -73,6 +74,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
size: 10, size: 10,
}) })
const childrenListOffsetCount = ref(0)
const isChildrenLoading = ref(false) const isChildrenLoading = ref(false)
const isChildrenListLoading = ref<Array<boolean>>([]) const isChildrenListLoading = ref<Array<boolean>>([])
@ -114,7 +117,6 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
.join('___'), .join('___'),
) )
// actions
const getRelatedTableRowId = (row: Record<string, any>) => { const getRelatedTableRowId = (row: Record<string, any>) => {
return relatedTableMeta.value?.columns return relatedTableMeta.value?.columns
?.filter((c) => c.pk) ?.filter((c) => c.pk)
@ -122,6 +124,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
.join('___') .join('___')
} }
// actions
const loadRelatedTableMeta = async () => { const loadRelatedTableMeta = async () => {
await getMeta(colOptions.value.fk_related_model_id as string) await getMeta(colOptions.value.fk_related_model_id as string)
} }
@ -182,6 +186,13 @@ 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 {
let offset =
childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1) - childrenExcludedOffsetCount.value
if (offset < 0) {
offset = 0
childrenExcludedOffsetCount.value = 0
}
isChildrenExcludedLoading.value = true isChildrenExcludedLoading.value = true
if (isPublic.value) { if (isPublic.value) {
const router = useRouter() const router = useRouter()
@ -198,7 +209,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
}, },
query: { query: {
limit: childrenExcludedListPagination.size, limit: childrenExcludedListPagination.size,
offset: childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1), offset,
where: where:
childrenExcludedListPagination.query && childrenExcludedListPagination.query &&
`(${relatedTableDisplayValueProp.value},like,${childrenExcludedListPagination.query})`, `(${relatedTableDisplayValueProp.value},like,${childrenExcludedListPagination.query})`,
@ -215,7 +226,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
relatedTableMeta?.value?.id as string, relatedTableMeta?.value?.id as string,
{ {
limit: childrenExcludedListPagination.size, limit: childrenExcludedListPagination.size,
offset: childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1), offset,
where: where:
childrenExcludedListPagination.query && childrenExcludedListPagination.query &&
`(${relatedTableDisplayValueProp.value},like,${childrenExcludedListPagination.query})`, `(${relatedTableDisplayValueProp.value},like,${childrenExcludedListPagination.query})`,
@ -232,7 +243,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
column?.value?.id, column?.value?.id,
{ {
limit: String(childrenExcludedListPagination.size), limit: String(childrenExcludedListPagination.size),
offset: String(childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1)), offset: String(offset),
// todo: where clause is missing from type // todo: where clause is missing from type
where: where:
childrenExcludedListPagination.query && childrenExcludedListPagination.query &&
@ -278,6 +289,15 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
isChildrenLoading.value = true 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
let offset = childrenListPagination.size * (childrenListPagination.page - 1) + childrenListOffsetCount.value
if (offset < 0) {
offset = 0
childrenListOffsetCount.value = 0
} else if (offset >= childrenListCount.value) {
offset = 0
}
if (isPublic.value) { if (isPublic.value) {
childrenList.value = await $api.public.dataNestedList( childrenList.value = await $api.public.dataNestedList(
sharedView.value?.uuid as string, sharedView.value?.uuid as string,
@ -286,7 +306,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
column.value.id, column.value.id,
{ {
limit: String(childrenListPagination.size), limit: String(childrenListPagination.size),
offset: String(childrenListPagination.size * (childrenListPagination.page - 1)), offset: String(offset),
where: where:
childrenListPagination.query && `(${relatedTableDisplayValueProp.value},like,${childrenListPagination.query})`, childrenListPagination.query && `(${relatedTableDisplayValueProp.value},like,${childrenListPagination.query})`,
} as any, } as any,
@ -306,7 +326,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
column?.value?.id, column?.value?.id,
{ {
limit: String(childrenListPagination.size), limit: String(childrenListPagination.size),
offset: String(childrenListPagination.size * (childrenListPagination.page - 1)), offset: String(offset),
where: where:
childrenListPagination.query && `(${relatedTableDisplayValueProp.value},like,${childrenListPagination.query})`, childrenListPagination.query && `(${relatedTableDisplayValueProp.value},like,${childrenListPagination.query})`,
} as any, } as any,
@ -349,7 +369,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
return false return false
} }
reloadData?.({ shouldShowLoading: false }) _reloadData?.({ shouldShowLoading: false })
/** reload child list if not a new row */ /** reload child list if not a new row */
if (!isNewRow?.value) { if (!isNewRow?.value) {
@ -386,6 +406,10 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
// } // }
try { try {
// todo: audit // todo: audit
childrenListOffsetCount.value = childrenListOffsetCount.value - 1
childrenExcludedOffsetCount.value = childrenExcludedOffsetCount.value - 1
isChildrenExcludedListLoading.value[index] = true isChildrenExcludedListLoading.value[index] = true
isChildrenListLoading.value[index] = true isChildrenListLoading.value[index] = true
await $api.dbTableRow.nestedRemove( await $api.dbTableRow.nestedRemove(
@ -420,14 +444,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 {
// To Keep the Loading State for Minimum 600ms
setTimeout(() => {
isChildrenExcludedListLoading.value[index] = false isChildrenExcludedListLoading.value[index] = false
isChildrenListLoading.value[index] = false isChildrenListLoading.value[index] = false
}, 600)
} }
reloadData?.({ shouldShowLoading: false }) _reloadData?.({ shouldShowLoading: false })
$e('a:links:unlink') $e('a:links:unlink')
} }
@ -435,7 +456,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
row: Record<string, any>, row: Record<string, any>,
{ metaValue = meta.value }: { metaValue?: TableType } = {}, { metaValue = meta.value }: { metaValue?: TableType } = {},
undo = false, undo = false,
index: number, index: number, // Index is For Loading and Linked State of Row
) => { ) => {
// todo: handle new record // todo: handle new record
// const pid = this._extractRowId(parent, this.parentMeta); // const pid = this._extractRowId(parent, this.parentMeta);
@ -454,6 +475,9 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
isChildrenExcludedListLoading.value[index] = true isChildrenExcludedListLoading.value[index] = true
isChildrenListLoading.value[index] = true isChildrenListLoading.value[index] = true
childrenListOffsetCount.value = childrenListOffsetCount.value + 1
childrenExcludedOffsetCount.value = childrenExcludedOffsetCount.value + 1
await $api.dbTableRow.nestedAdd( await $api.dbTableRow.nestedAdd(
NOCO, NOCO,
base.value.id as string, base.value.id as string,
@ -480,6 +504,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} }
isChildrenExcludedListLinked.value[index] = true isChildrenExcludedListLinked.value[index] = true
isChildrenListLinked.value[index] = true isChildrenListLinked.value[index] = true
if (colOptions.value.type !== 'bt') { if (colOptions.value.type !== 'bt') {
childrenListCount.value = childrenListCount.value + 1 childrenListCount.value = childrenListCount.value + 1
} else { } else {
@ -491,13 +516,11 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
} finally { } finally {
// To Keep the Loading State for Minimum 600ms // To Keep the Loading State for Minimum 600ms
setTimeout(() => {
isChildrenExcludedListLoading.value[index] = false isChildrenExcludedListLoading.value[index] = false
isChildrenListLoading.value[index] = false isChildrenListLoading.value[index] = false
}, 600)
} }
reloadData?.({ shouldShowLoading: false }) _reloadData?.({ shouldShowLoading: false })
$e('a:links:link') $e('a:links:link')
} }
@ -525,6 +548,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
childrenExcludedList, childrenExcludedList,
childrenList, childrenList,
childrenListCount, childrenListCount,
childrenListOffsetCount,
childrenExcludedOffsetCount,
rowId, rowId,
childrenExcludedListPagination, childrenExcludedListPagination,
childrenListPagination, childrenListPagination,

2
tests/playwright/pages/Dashboard/ExpandedForm/index.ts

@ -68,7 +68,7 @@ export class ExpandedFormPage extends BasePage {
} }
async fillField({ columnTitle, value, type = 'text' }: { columnTitle: string; value: string; type?: string }) { async fillField({ columnTitle, value, type = 'text' }: { columnTitle: string; value: string; type?: string }) {
const field = this.get().locator(`[data-testid="nc-expand-col-${columnTitle}"]`); const field = this.get().getByTestId(`nc-expand-col-${columnTitle}`);
switch (type) { switch (type) {
case 'text': case 'text':
await field.locator('input').fill(value); await field.locator('input').fill(value);

1
tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts

@ -45,7 +45,6 @@ test.describe('LTAR create & update', () => {
childTable: 'Sheet2', childTable: 'Sheet2',
relationType: 'Many To many', relationType: 'Many To many',
}); });
await dashboard.closeTab({ title: 'Sheet1' });
await dashboard.treeView.openTable({ title: 'Sheet2', networkResponse: false }); await dashboard.treeView.openTable({ title: 'Sheet2', networkResponse: false });
await dashboard.grid.column.create({ await dashboard.grid.column.create({

Loading…
Cancel
Save