Browse Source

Nc fix: linked records dropdown followup (#8415)

* fix(nc-gui): update link record dropdown border radius

* fix(nc-gui): move link/unlink btn to the right side

* fix(nc-gui): update placeholder text color

* fix(nc-gui): reduce link record dropdown bottom bar padding x

* fix(nc-gui): update expanded form save btn text in create new record from link record

* fix(nc-gui): grid active cell border issue

* fix(nc-gui): link record dropdown trigger & dropdown spacing

* fix(nc-gui): link record footer btn size

* fix(nc-gui): link record loading state

* fix(nc-gui): update link record search input icon

* fix(nc-gui): update link record item skeleton

* fix(nc-gui): update grid row hover & selected state bg color

* fix(nc-gui): add tooltip on hover over expand icon

* fix(nc-gui): link record tooltip issue

* fix(nc-gui): update search query empty state status

* fix(nc-gui): bt cell ui fixes

* feat(nc-gui): resizable link record dropdown

* fix(nc-gui): external DB, fields re-order to re-align sub fields needs a fix

* chore(nc-gui): lint

* fix(nc-gui): bt & oto cell unlink & link btn visibility issue

* fix(nc-gui): pr review changes

* fix: docs

---------

Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/8433/head
Ramesh Mane 2 months ago committed by GitHub
parent
commit
d4fa6a8174
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  2. 20
      packages/nc-gui/components/smartsheet/grid/Table.vue
  3. 4
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  4. 2
      packages/nc-gui/components/virtual-cell/HasMany.vue
  5. 2
      packages/nc-gui/components/virtual-cell/Links.vue
  6. 2
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  7. 10
      packages/nc-gui/components/virtual-cell/OneToOne.vue
  8. 67
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  9. 13
      packages/nc-gui/components/virtual-cell/components/LinkRecordDropdown.vue
  10. 49
      packages/nc-gui/components/virtual-cell/components/LinkedItems.vue
  11. 73
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  12. 52
      packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue
  13. 1
      packages/nc-gui/lang/en.json
  14. 8
      packages/noco-docs/docs/070.fields/040.field-types/040.links-based/010.links.md
  15. BIN
      packages/noco-docs/static/img/v2/fields/add-link-modal.png
  16. BIN
      packages/noco-docs/static/img/v2/fields/linked-record-modal.png
  17. 37
      packages/nocodb/src/models/Column.ts

3
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -26,6 +26,7 @@ interface Props {
closeAfterSave?: boolean
newRecordHeader?: string
skipReload?: boolean
newRecordSubmitBtnText?: string
}
const props = defineProps<Props>()
@ -882,7 +883,7 @@ export default {
size="medium"
@click="save"
>
<div class="xs:px-1">Save</div>
<div class="xs:px-1">{{ newRecordSubmitBtnText ?? 'Save' }}</div>
</NcButton>
</div>
</div>

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

@ -427,8 +427,8 @@ const cellMeta = computed(() => {
const colMeta = computed(() => {
return fields.value.map((col) => {
return {
isLookup: isLinksOrLTAR(col),
isRollup: isBt(col),
isLookup: isLookup(col),
isRollup: isRollup(col),
isFormula: isFormula(col),
isCreatedOrLastModifiedTimeCol: isCreatedOrLastModifiedTimeCol(col),
isCreatedOrLastModifiedByCol: isCreatedOrLastModifiedByCol(col),
@ -857,7 +857,7 @@ onClickOutside(tableBodyEl, (e) => {
// ignore unselecting if clicked inside or on the picker(Date, Time, DateTime, Year)
// or single/multi select options
const activePickerOrDropdownEl = document.querySelector(
'.nc-picker-datetime.active,.nc-dropdown-single-select-cell.active,.nc-dropdown-multi-select-cell.active,.nc-dropdown-user-select-cell.active,.nc-picker-date.active,.nc-picker-year.active,.nc-picker-time.active',
'.nc-picker-datetime.active,.nc-dropdown-single-select-cell.active,.nc-dropdown-multi-select-cell.active,.nc-dropdown-user-select-cell.active,.nc-picker-date.active,.nc-picker-year.active,.nc-picker-time.active,.nc-link-dropdown-root',
)
if (
e.target &&
@ -1892,6 +1892,7 @@ onKeyStroke('ArrowDown', onDown)
:class="{
'active-row': activeCell.row === rowIndex || selectedRange._start?.row === rowIndex,
'mouse-down': isGridCellMouseDown || isFillMode,
'selected-row': row.rowMeta.selected,
}"
:style="{ height: rowHeight ? `${rowHeightInPx[`${rowHeight}`]}px` : `${rowHeightInPx['1']}px` }"
:data-testid="`grid-row-${rowIndex}`"
@ -2430,7 +2431,7 @@ onKeyStroke('ArrowDown', onDown)
td,
th {
@apply border-gray-100 border-solid border-r bg-gray-50 p-0;
@apply border-gray-100 border-solid border-r bg-gray-100 p-0;
min-height: 32px !important;
height: 32px !important;
position: relative;
@ -2685,9 +2686,18 @@ onKeyStroke('ArrowDown', onDown)
@apply !xs:hidden flex;
}
&:not(.selected-row) {
td.nc-grid-cell:not(.active),
td:nth-child(2):not(.active) {
@apply !bg-gray-50 border-b-gray-200 border-r-gray-200;
}
}
}
&.selected-row {
td.nc-grid-cell:not(.active),
td:nth-child(2):not(.active) {
@apply !bg-gray-50 border-b-gray-200 border-r-gray-200;
@apply !bg-[#F0F3FF] border-b-gray-200 border-r-gray-200;
}
}
}

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

@ -87,8 +87,8 @@ watch(value, (next) => {
<template>
<div class="flex w-full chips-wrapper items-center" :class="{ active }">
<LazyVirtualCellComponentsLinkRecordDropdown v-model:is-open="isOpen">
<div class="flex items-center w-full">
<div class="nc-cell-field chips flex items-center flex-1">
<div class="flex items-center w-full min-h-7.7">
<div class="nc-cell-field chips flex items-center flex-1 max-w-[calc(100%_-_16px)]">
<template v-if="value && (relatedTableDisplayValueProp || relatedTableDisplayValuePropId)">
<VirtualCellComponentsItemChip
:item="value"

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

@ -127,7 +127,7 @@ watch(
<template>
<LazyVirtualCellComponentsLinkRecordDropdown v-model:is-open="isOpen">
<div class="flex items-center gap-1 w-full chips-wrapper">
<div class="flex items-center gap-1 w-full chips-wrapper min-h-7.7">
<div class="chips flex items-center img-container flex-1 hm-items flex-nowrap min-w-0 overflow-hidden">
<template v-if="cells">
<VirtualCellComponentsItemChip

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

@ -139,7 +139,7 @@ watch(
<template>
<div class="nc-cell-field flex w-full group items-center nc-links-wrapper py-1" @dblclick.stop="openChildList">
<LazyVirtualCellComponentsLinkRecordDropdown v-model:is-open="isOpen">
<div class="flex w-full group items-center">
<div class="flex w-full group items-center min-h-7.7">
<div class="block flex-shrink truncate">
<component
:is="isUnderLookup ? 'span' : 'a'"

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

@ -126,7 +126,7 @@ watch(
<template>
<LazyVirtualCellComponentsLinkRecordDropdown v-model:is-open="isOpen">
<div class="flex items-center gap-1 w-full chips-wrapper">
<div class="flex items-center gap-1 w-full chips-wrapper min-h-7.7">
<div class="chips flex items-center img-container flex-1 hm-items flex-nowrap min-w-0 overflow-hidden">
<template v-if="cells">
<VirtualCellComponentsItemChip

10
packages/nc-gui/components/virtual-cell/OneToOne.vue

@ -84,8 +84,8 @@ watch(
<template>
<LazyVirtualCellComponentsLinkRecordDropdown v-model:is-open="isOpen">
<div class="flex w-full chips-wrapper items-center" :class="{ active }">
<div class="nc-cell-field chips flex items-center flex-1">
<div class="flex w-full chips-wrapper items-center min-h-7.7" :class="{ active }">
<div class="nc-cell-field chips flex items-center flex-1 max-w-[calc(100%_-_16px)]">
<template v-if="value && (relatedTableDisplayValueProp || relatedTableDisplayValuePropId)">
<VirtualCellComponentsItemChip
:item="value"
@ -109,7 +109,11 @@ watch(
>
<GeneralIcon
:icon="addIcon"
class="select-none !text-md text-gray-700 nc-action-icon nc-plus invisible group-hover:visible group-focus:visible"
class="select-none text-gray-700 nc-action-icon nc-plus invisible group-hover:visible group-focus:visible"
:class="{
'!text-[14px]': addIcon === 'expand',
'!text-md': addIcon !== 'expand',
}"
@click.stop="listItemsDlg = true"
/>
</div>

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

@ -28,6 +28,8 @@ const isForm = inject(IsFormInj)!
const { open } = useExpandedFormDetached()
function openExpandedForm() {
if (!active.value) return
const rowId = extractPkFromRow(item, relatedTableMeta.value.columns as ColumnType[])
if (!readOnly.value && !readonlyProp && rowId) {
open({
@ -50,44 +52,50 @@ export default {
<template>
<div
v-e="['c:row-expand:open']"
class="chip group mr-1 my-1 flex items-center rounded-[2px] flex-row"
class="chip group mr-1 my-1 flex items-center rounded-[2px] flex-row truncate"
:class="{ active, 'border-1 py-1 px-2': isAttachment(column) }"
@click="openExpandedForm"
>
<span class="name">
<!-- Render virtual cell -->
<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 class="text-ellipsis overflow-hidden">
<span class="name">
<!-- Render virtual cell -->
<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>
<!-- For attachment cell avoid adding chip style -->
<!-- Render normal cell -->
<template v-else>
<div
class="min-w-max"
:class="{
'px-1 rounded-full flex-1': !isAttachment(column),
'border-gray-200 rounded border-1':
border && ![UITypes.Attachment, UITypes.MultiSelect, UITypes.SingleSelect].includes(column.uidt),
}"
>
<LazySmartsheetCell :model-value="value" :column="column" :edit-enabled="false" :virtual="true" :read-only="true" />
<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':
border && ![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>
</template>
</span>
</span>
</div>
<div v-show="active || isForm" v-if="showUnlinkButton && !readOnly && isUIAllowed('dataEdit')" class="flex items-center">
<div
v-show="active || isForm"
v-if="showUnlinkButton && !readOnly && isUIAllowed('dataEdit')"
class="flex items-center cursor-pointer"
>
<component
: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-gray-500/50 group-hover:text-gray-500 ml-0.5 cursor-pointer"
@click.stop="emit('unlink')"
/>
</div>
@ -99,9 +107,8 @@ export default {
max-width: max(100%, 60px);
.name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
word-break: keep-all;
}
}
</style>

13
packages/nc-gui/components/virtual-cell/components/LinkRecordDropdown.vue

@ -68,7 +68,7 @@ watch([ncLinksDropdownRef, isOpen], () => {
>
<slot />
<template #overlay>
<div ref="ncLinksDropdownRef" class="h-[412px] w-[540px]" :class="`${randomClass}`">
<div ref="ncLinksDropdownRef" class="h-[412px] w-[540px] nc-links-dropdown-wrapper" :class="`${randomClass}`">
<slot name="overlay" />
</div>
</template>
@ -77,9 +77,20 @@ watch([ncLinksDropdownRef, isOpen], () => {
<style lang="scss">
.nc-links-dropdown {
@apply rounded-xl !border-gray-200;
z-index: 1000 !important;
}
.nc-link-dropdown-root {
z-index: 1000;
}
.nc-links-dropdown-wrapper {
@apply h-[412px] w-[540px];
overflow-y: auto;
overflow-x: hidden;
resize: vertical;
min-height: 412px;
max-height: 700px;
max-width: 540px;
}
</style>

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

@ -306,15 +306,14 @@ const onFilterChange = () => {
<template>
<div class="nc-modal-child-list h-full w-full" :class="{ active: vModel }" @keydown.enter.stop>
<div class="flex flex-col h-full">
<div class="nc-dropdown-link-record-header bg-gray-100 py-2 rounded-t-md flex justify-between pl-3 pr-2 gap-2">
<div class="nc-dropdown-link-record-header bg-gray-100 py-2 rounded-t-xl flex justify-between pl-3 pr-2 gap-2">
<div v-if="!isForm" class="flex-1 nc-dropdown-link-record-search-wrapper flex items-center py-0.5 rounded-md">
<MdiMagnify class="nc-search-icon w-5 h-5" />
<a-input
ref="filterQueryRef"
v-model:value="childrenListPagination.query"
:bordered="false"
placeholder="Search linked records..."
class="w-full min-h-4"
class="w-full min-h-4 !pl-0"
size="small"
@change="onFilterChange"
@keydown.capture.stop="
@ -325,6 +324,9 @@ const onFilterChange = () => {
}
"
>
<template #prefix>
<GeneralIcon icon="search" class="nc-search-icon mr-2 h-4 w-4 text-gray-500" />
</template>
</a-input>
</div>
<div v-else>&nbsp;</div>
@ -343,26 +345,21 @@ const onFilterChange = () => {
<div
v-for="(_x, i) in Array.from({ length: skeletonCount })"
:key="i"
class="flex flex-row gap-2 mb-2 transition-all relative !border-gray-200 hover:bg-gray-50"
class="flex flex-row gap-3 px-3 py-2 transition-all relative border-b-1 border-gray-200 hover:bg-gray-50"
>
<div class="flex items-center">
<a-skeleton-image class="h-14 w-14 !rounded-xl children:!h-full" />
<a-skeleton-image class="!h-11 !w-11 !rounded-md overflow-hidden children:(!h-full !w-full)" />
</div>
<div class="flex flex-col gap-2 flex-grow justify-center">
<a-skeleton-input active class="h-3 !w-48 !rounded-xl" size="small" />
<a-skeleton-input active class="h-4 !w-48 !rounded-md overflow-hidden" size="small" />
<div class="flex flex-row gap-6 w-10/12">
<div class="flex flex-col gap-0.5">
<a-skeleton-input active class="!h-2 !w-12" size="small" />
<a-skeleton-input active class="!h-2 !w-24" size="small" />
</div>
<div class="flex flex-col gap-0.5">
<a-skeleton-input active class="!h-2 !w-12" size="small" />
<a-skeleton-input active class="!h-2 !w-24" size="small" />
</div>
<div class="flex flex-col gap-0.5">
<a-skeleton-input active class="!h-2 !w-12" size="small" />
<a-skeleton-input active class="!h-2 !w-24" size="small" />
</div>
<a-skeleton-input
v-for="idx of [1, 2, 3]"
:key="idx"
active
class="!h-3 !w-24 !rounded-md overflow-hidden"
size="small"
/>
</div>
</div>
</div>
@ -410,13 +407,13 @@ const onFilterChange = () => {
</div>
</div>
<div class="bg-gray-100 px-3 py-2 rounded-b-md flex items-center justify-between gap-3 min-h-12">
<div class="nc-dropdown-link-record-footer bg-gray-100 p-2 rounded-b-xl flex items-center justify-between gap-3 min-h-11">
<div class="flex items-center gap-2">
<NcButton
v-if="!isPublic"
v-e="['c:row-expand:open']"
size="small"
class="!hover:(bg-white text-brand-500)"
class="!hover:(bg-white text-brand-500) !h-7 !text-small"
type="secondary"
@click="addNewRecord"
>
@ -428,7 +425,7 @@ const onFilterChange = () => {
v-if="!readOnly && (childrenListCount > 0 || (childrenList?.list ?? state?.[colTitle] ?? []).length > 0)"
v-e="['c:links:link']"
data-testid="nc-child-list-button-link-to"
class="!hover:(bg-white text-brand-500)"
class="!hover:(bg-white text-brand-500) !h-7 !text-small"
size="small"
type="secondary"
@click="emit('attachRecord')"
@ -478,6 +475,7 @@ const onFilterChange = () => {
:state="newRowState"
:row-id="extractPkFromRow(expandedFormRow, relatedTableMeta.columns as ColumnType[])"
use-meta-fields
new-record-submit-btn-text="Create & Link"
@created-record="onCreatedRecord"
/>
</Suspense>
@ -493,8 +491,8 @@ const onFilterChange = () => {
@apply !p-0;
}
:deep(.ant-skeleton-element .ant-skeleton-image) {
@apply !h-full;
:deep(.ant-skeleton-element .ant-skeleton-image-svg) {
@apply !w-7;
}
</style>
@ -509,5 +507,10 @@ const onFilterChange = () => {
@apply text-gray-600;
}
}
input {
&::placeholder {
@apply text-gray-500;
}
}
}
</style>

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

@ -90,25 +90,6 @@ const displayValue = computed(() => {
:hoverable="false"
>
<div class="flex items-center gap-3">
<div v-if="isLoading" class="flex">
<MdiLoading class="flex-none w-7 h-7 !text-brand-500 animate-spin" />
</div>
<NcTooltip v-else class="z-10 flex">
<template #title> {{ isLinked ? 'Unlink' : 'Link' }}</template>
<button
tabindex="-1"
class="nc-list-item-link-unlink-btn p-1.5 flex rounded-lg transition-all"
:class="{
'bg-red-100 text-red-500 hover:bg-red-200': isLinked,
'bg-green-100 text-green-500 hover:bg-green-200': !isLinked,
}"
@click="$emit('linkOrUnlink')"
>
<GeneralIcon :icon="isLinked ? 'minus' : 'plus'" class="flex-none w-4 h-4 !font-extrabold" />
</button>
</NcTooltip>
<template v-if="attachment">
<div v-if="attachments && attachments.length">
<a-carousel autoplay class="!w-11 !h-11 !max-h-11 !max-w-11">
@ -144,15 +125,20 @@ const displayValue = computed(() => {
>
<div v-for="field in fields" :key="field.id" class="sm:(w-1/3 max-w-1/3 overflow-hidden)">
<div v-if="!isRowEmpty(row, field)" class="flex flex-col gap-[-1]">
<NcTooltip class="z-10 flex" placement="bottom">
<NcTooltip class="z-10 flex" placement="bottomLeft" :arrow-point-at-center="false">
<template #title>
<LazySmartsheetHeaderVirtualCell
v-if="isVirtualCol(field)"
class="!scale-60 text-gray-100 !text-sm"
class="text-gray-100 !text-sm nc-link-record-cell-tooltip"
:column="field"
:hide-menu="true"
/>
<LazySmartsheetHeaderCell
v-else
class="text-gray-100 !text-sm nc-link-record-cell-tooltip"
:column="field"
:hide-menu="true"
/>
<LazySmartsheetHeaderCell v-else class="!scale-70 text-gray-100 !text-sm" :column="field" :hide-menu="true" />
</template>
<div class="nc-link-record-cell flex w-full max-w-full">
<LazySmartsheetVirtualCell v-if="isVirtualCol(field)" v-model="row[field.title]" :row="row" :column="field" />
@ -171,15 +157,38 @@ const displayValue = computed(() => {
</div>
</div>
<div v-if="!isForm && !isPublic && !readOnly" class="flex-none flex items-center w-7">
<NcTooltip class="flex">
<template #title>{{ $t('title.expand') }}</template>
<button
v-e="['c:row-expand:open']"
:tabindex="-1"
class="z-10 flex items-center justify-center nc-expand-item !group-hover:visible !invisible !h-7 !w-7 transition-all !hover:children:(w-4.5 h-4.5)"
@click.stop="$emit('expand', row)"
>
<MaximizeIcon class="flex-none w-4 h-4 scale-125" />
</button>
</NcTooltip>
</div>
<NcTooltip class="z-10 flex">
<template #title> {{ isLinked ? 'Unlink' : 'Link' }}</template>
<button
v-e="['c:row-expand:open']"
:tabindex="-1"
class="z-10 flex items-center justify-center nc-expand-item !group-hover:visible !invisible !h-7 !w-7 transition-all !hover:children:(w-4.5 h-4.5)"
@click.stop="$emit('expand', row)"
tabindex="-1"
class="nc-list-item-link-unlink-btn p-1.5 flex rounded-lg transition-all"
:class="{
'bg-gray-200 text-gray-800 hover:(bg-red-100 text-red-500)': isLinked,
'bg-green-[#D4F7E0] text-[#17803D] hover:bg-green-200': !isLinked,
}"
@click="$emit('linkOrUnlink')"
>
<MaximizeIcon class="flex-none w-4 h-4 scale-125" />
<div v-if="isLoading" class="flex">
<MdiLoading class="flex-none w-4 h-4 !text-brand-500 animate-spin" />
</div>
<GeneralIcon v-else :icon="isLinked ? 'minus' : 'plus'" class="flex-none w-4 h-4 !font-extrabold" />
</button>
</div>
</NcTooltip>
</div>
</a-card>
</div>
@ -260,6 +269,14 @@ const displayValue = computed(() => {
}
}
}
.nc-link-record-cell-tooltip {
:deep(.nc-cell-icon) {
@apply !ml-0;
}
:deep(.name) {
@apply !text-small;
}
}
</style>
<style lang="scss">

52
packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue

@ -295,7 +295,7 @@ const onFilterChange = () => {
<template>
<div class="nc-modal-link-record h-full w-full overflow-hidden" :class="{ active: vModel }" @keydown.enter.stop>
<div class="flex flex-col h-full">
<div class="nc-dropdown-link-record-header bg-gray-100 py-2 rounded-t-md flex justify-between pl-3 pr-2 gap-2">
<div class="nc-dropdown-link-record-header bg-gray-100 py-2 rounded-t-xl flex justify-between pl-3 pr-2 gap-2">
<div class="flex-1 gap-2 flex items-center">
<button
v-if="!hideBackBtn"
@ -306,13 +306,12 @@ const onFilterChange = () => {
</button>
<div class="flex-1 nc-dropdown-link-record-search-wrapper flex items-center py-0.5 rounded-md">
<MdiMagnify class="nc-search-icon w-5 h-5" />
<a-input
ref="filterQueryRef"
v-model:value="childrenExcludedListPagination.query"
:bordered="false"
placeholder="Search records to link..."
class="w-full nc-excluded-search min-h-4"
class="w-full nc-excluded-search min-h-4 !pl-0"
size="small"
@change="onFilterChange"
@keydown.capture.stop="
@ -323,6 +322,9 @@ const onFilterChange = () => {
}
"
>
<template #prefix>
<GeneralIcon icon="search" class="nc-search-icon mr-2 h-4 w-4 text-gray-500" />
</template>
</a-input>
</div>
</div>
@ -341,26 +343,21 @@ const onFilterChange = () => {
<div
v-for="(_x, i) in Array.from({ length: 10 })"
:key="i"
class="flex flex-row gap-2 mb-2 transition-all relative !border-gray-200 hover:bg-gray-50"
class="flex flex-row gap-3 px-3 py-2 transition-all relative border-b-1 border-gray-200 hover:bg-gray-50"
>
<div class="flex items-center">
<a-skeleton-image class="h-14 w-14 !rounded-xl children:!h-full" />
<a-skeleton-image class="!h-11 !w-11 !rounded-md overflow-hidden children:(!h-full !w-full)" />
</div>
<div class="flex flex-col gap-2 flex-grow justify-center">
<a-skeleton-input active class="h-3 !w-48 !rounded-xl" size="small" />
<a-skeleton-input active class="h-4 !w-48 !rounded-md overflow-hidden" size="small" />
<div class="flex flex-row gap-6 w-10/12">
<div class="flex flex-col gap-0.5">
<a-skeleton-input active class="!h-2 !w-12" size="small" />
<a-skeleton-input active class="!h-2 !w-24" size="small" />
</div>
<div class="flex flex-col gap-0.5">
<a-skeleton-input active class="!h-2 !w-12" size="small" />
<a-skeleton-input active class="!h-2 !w-24" size="small" />
</div>
<div class="flex flex-col gap-0.5">
<a-skeleton-input active class="!h-2 !w-12" size="small" />
<a-skeleton-input active class="!h-2 !w-24" size="small" />
</div>
<a-skeleton-input
v-for="idx of [1, 2, 3]"
:key="idx"
active
class="!h-3 !w-24 !rounded-md overflow-hidden"
size="small"
/>
</div>
</div>
</div>
@ -392,19 +389,21 @@ const onFilterChange = () => {
</template>
<div v-else class="h-full my-auto py-2 flex flex-col gap-3 items-center justify-center text-gray-500">
<InboxIcon class="w-16 h-16 mx-auto" />
<p>
<p v-if="childrenExcludedListPagination.query">{{ $t('msg.noRecordsMatchYourSearchQuery') }}</p>
<p v-else>
{{ $t('msg.thereAreNoRecordsInTable') }}
{{ relatedTableMeta?.title }}
</p>
</div>
</div>
<div class="bg-gray-100 px-3 py-2 rounded-b-md flex items-center justify-between min-h-12">
<div class="nc-dropdown-link-record-footer bg-gray-100 p-2 rounded-b-xl flex items-center justify-between min-h-11">
<div class="flex">
<NcButton
v-if="!isPublic"
v-e="['c:row-expand:open']"
size="small"
class="!hover:(bg-white text-brand-500)"
class="!hover:(bg-white text-brand-500) !h-7 !text-small"
type="secondary"
@click="addNewRecord"
>
@ -463,6 +462,7 @@ const onFilterChange = () => {
:state="newRowState"
use-meta-fields
:skip-reload="true"
new-record-submit-btn-text="Create & Link"
@created-record="onCreatedRecord"
/>
</Suspense>
@ -470,8 +470,8 @@ const onFilterChange = () => {
</template>
<style lang="scss" scoped>
:deep(.ant-skeleton-element .ant-skeleton-image) {
@apply !h-full;
:deep(.ant-skeleton-element .ant-skeleton-image-svg) {
@apply !w-7;
}
</style>
@ -486,5 +486,11 @@ const onFilterChange = () => {
@apply text-gray-600;
}
}
input {
&::placeholder {
@apply text-gray-500;
}
}
}
</style>

1
packages/nc-gui/lang/en.json

@ -1200,6 +1200,7 @@
"noNewNotifications": "You have no new notifications",
"noRecordFound": "Record not found",
"noRecordsFound": "No records found",
"noRecordsMatchYourSearchQuery": "No records match your search query",
"rowDeleted": "Record deleted",
"saveChanges": "Do you want to save the changes?",
"tooLargeFieldEntity": "The field is too large to be converted to {entity}",

8
packages/noco-docs/docs/070.fields/040.field-types/040.links-based/010.links.md

@ -47,8 +47,8 @@ List of linked records will appear as a dropdown containing linked records as ca
1. Search bar, to narrow down the list of linked records displayed
2. Icon represents `Many-to-Many` relation. The count indicates the number of linked records
3. List (cards) of linked records
4. Click on the `-` icon to unlink the record
5. To view additional information (expanded record), hover on the card & click on the `<>` icon
4. To view additional information (expanded record), hover on the card & click on the `<>` icon
5. Click on the `-` icon to unlink the record
6. Click on `+ New record` button to create and link a new record to the current one
7. Click on `Link more records` to link an existing record [Read more](#link-new-records)
8. Pagination bar
@ -65,8 +65,8 @@ A brief note about the modal components:
1. Search bar, to narrow down the list of records displayed
2. Icon represents `Many-to-Many` relation. The count indicates the number of linked records
3. List (cards) of records available for linking
4. Click on the `+` icon to link the record
5. To view additional information (expanded record) before linking, hover on the card & click on the `<>` icon
4. To view additional information (expanded record) before linking, hover on the card & click on the `<>` icon
5. Click on the `+` icon to link the record
6. Click on `+ New record` button to create and link a new record to the current one
7. Pagination bar

BIN
packages/noco-docs/static/img/v2/fields/add-link-modal.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 362 KiB

BIN
packages/noco-docs/static/img/v2/fields/linked-record-modal.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 368 KiB

37
packages/nocodb/src/models/Column.ts

@ -498,6 +498,16 @@ export default class Column<T = any> implements ColumnType {
]);
let { list: columnsList } = cachedList;
const { isNoneList } = cachedList;
const defaultViewColumns = fk_default_view_id
? await View.getColumns(fk_default_view_id)
: [];
const defaultViewColumnOrderMap = defaultViewColumns.reduce((acc, col) => {
acc[col.fk_column_id] = col.order;
return acc;
}, {} as Record<string, number>);
if (!isNoneList && !columnsList.length) {
columnsList = await ncMeta.metaList2(null, null, MetaTable.COLUMNS, {
condition: {
@ -507,38 +517,29 @@ export default class Column<T = any> implements ColumnType {
order: 'asc',
},
});
const defaultViewColumns = fk_default_view_id
? await View.getColumns(fk_default_view_id)
: [];
const defaultViewColumnOrderMap = defaultViewColumns.reduce(
(acc, col) => {
acc[col.fk_column_id] = col.order;
return acc;
},
{} as Record<string, number>,
);
columnsList.forEach((column) => {
column.meta = parseMetaProp(column);
if (defaultViewColumns.length) {
column.meta = {
...column.meta,
defaultViewColOrder: defaultViewColumnOrderMap[column.id],
};
}
});
await NocoCache.setList(CacheScope.COLUMN, [fk_model_id], columnsList);
}
columnsList.sort(
(a, b) =>
(a.order != null ? a.order : Infinity) -
(b.order != null ? b.order : Infinity),
);
return Promise.all(
columnsList.map(async (m) => {
if (defaultViewColumns.length) {
m.meta = {
...parseMetaProp(m),
defaultViewColOrder: defaultViewColumnOrderMap[m.id],
};
}
const column = new Column(m);
await column.getColOptions(ncMeta);
return column;

Loading…
Cancel
Save