Browse Source

Merge pull request #9986 from nocodb/nc-fix/limit-display-value-col

Nc fix/limit display value col
pull/10015/head
Raju Udava 2 days ago committed by GitHub
parent
commit
c6fdb2e243
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 47
      packages/nc-gui/components/nc/List/index.vue
  2. 2
      packages/nc-gui/components/smartsheet/column/ButtonOptions.vue
  3. 53
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  4. 32
      packages/nc-gui/components/smartsheet/column/LongTextOptions.vue
  5. 2
      packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
  6. 38
      packages/nc-gui/components/smartsheet/header/Menu.vue
  7. 80
      packages/nc-gui/components/smartsheet/header/UpdateDisplayValue.vue
  8. 80
      packages/nocodb-sdk/src/lib/UITypes.ts
  9. 20
      packages/nocodb-sdk/src/lib/helperFunctions.ts
  10. 2
      packages/nocodb-sdk/src/lib/index.ts

47
packages/nc-gui/components/nc/List/index.vue

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useVirtualList } from '@vueuse/core' import { useVirtualList } from '@vueuse/core'
import type { TooltipPlacement } from 'ant-design-vue/lib/tooltip'
export type MultiSelectRawValueType = Array<string | number> export type MultiSelectRawValueType = Array<string | number>
@ -8,6 +9,8 @@ export type RawValueType = string | number | MultiSelectRawValueType
export interface NcListItemType { export interface NcListItemType {
value?: RawValueType value?: RawValueType
label?: string label?: string
disabled?: boolean
ncItemTooltip?: string
[key: string]: any [key: string]: any
} }
@ -61,6 +64,8 @@ export interface NcListProps {
containerClassName?: string containerClassName?: string
itemClassName?: string itemClassName?: string
itemTooltipPlacement?: TooltipPlacement
} }
interface Emits { interface Emits {
@ -82,6 +87,7 @@ const props = withDefaults(defineProps<NcListProps>(), {
minItemsForSearch: 4, minItemsForSearch: 4,
containerClassName: '', containerClassName: '',
itemClassName: '', itemClassName: '',
itemTooltipPlacement: 'right',
}) })
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
@ -190,7 +196,7 @@ const handleResetHoverEffect = (clearActiveOption = false, newActiveIndex?: numb
* It updates the model value, emits a change event, and optionally closes the dropdown. * It updates the model value, emits a change event, and optionally closes the dropdown.
*/ */
const handleSelectOption = (option: NcListItemType, index?: number) => { const handleSelectOption = (option: NcListItemType, index?: number) => {
if (!option?.[optionValueKey]) return if (!option?.[optionValueKey] || option?.disabled) return
if (index !== undefined) { if (index !== undefined) {
activeOptionIndex.value = index activeOptionIndex.value = index
@ -228,6 +234,9 @@ const handleAutoScrollOption = (useDelay = false) => {
}, 150) }, 150)
} }
// Todo: skip arrowUp/.arrowDown on disabled options
// const getNextEnabledOptionIndex = (currentIndex: number, increment = true) => {}
const onArrowDown = () => { const onArrowDown = () => {
keyDown.value = true keyDown.value = true
handleResetHoverEffect() handleResetHoverEffect()
@ -364,37 +373,51 @@ watch(
:class="containerClassName" :class="containerClassName"
> >
<div v-bind="wrapperProps"> <div v-bind="wrapperProps">
<div <NcTooltip
v-for="{ data: option, index: idx } in virtualList" v-for="{ data: option, index: idx } in virtualList"
:key="idx" :key="idx"
class="flex items-center gap-2 w-full py-2 px-2 hover:bg-gray-100 cursor-pointer rounded-md" class="flex items-center gap-2 w-full py-2 px-2 rounded-md"
:class="[ :class="[
`nc-list-option-${idx}`, `nc-list-option-${idx}`,
{ {
'nc-list-option-selected': compareVModel(option[optionValueKey]), 'nc-list-option-selected': compareVModel(option[optionValueKey]),
'bg-gray-100 ': showHoverEffectOnSelectedOption && compareVModel(option[optionValueKey]), 'bg-gray-100 ': !option?.disabled && showHoverEffectOnSelectedOption && compareVModel(option[optionValueKey]),
'bg-gray-100 nc-list-option-active': activeOptionIndex === idx, 'bg-gray-100 nc-list-option-active': !option?.disabled && activeOptionIndex === idx,
'opacity-60 cursor-not-allowed': option?.disabled,
'hover:bg-gray-100 cursor-pointer': !option?.disabled,
}, },
`${itemClassName}`, `${itemClassName}`,
]" ]"
:placement="itemTooltipPlacement"
:disabled="!option?.ncItemTooltip"
@mouseover="handleResetHoverEffect(true, idx)" @mouseover="handleResetHoverEffect(true, idx)"
@click="handleSelectOption(option, idx)" @click="handleSelectOption(option, idx)"
> >
<template #title>{{ option.ncItemTooltip }} </template>
<slot name="listItem" :option="option" :is-selected="() => compareVModel(option[optionValueKey])" :index="idx"> <slot name="listItem" :option="option" :is-selected="() => compareVModel(option[optionValueKey])" :index="idx">
<slot name="listItemExtraLeft" :option="option" :is-selected="() => compareVModel(option[optionValueKey])">
</slot>
<NcTooltip class="truncate flex-1" show-on-truncate-only> <NcTooltip class="truncate flex-1" show-on-truncate-only>
<template #title> <template #title>
{{ option[optionLabelKey] }} {{ option[optionLabelKey] }}
</template> </template>
{{ option[optionLabelKey] }} {{ option[optionLabelKey] }}
</NcTooltip> </NcTooltip>
<GeneralIcon
v-if="showSelectedOption && compareVModel(option[optionValueKey])" <slot name="listItemExtraRight" :option="option" :is-selected="() => compareVModel(option[optionValueKey])">
id="nc-selected-item-icon" </slot>
icon="check"
class="flex-none text-primary w-4 h-4" <slot name="listItemSelectedIcon" :option="option" :is-selected="() => compareVModel(option[optionValueKey])">
/> <GeneralIcon
v-if="showSelectedOption && compareVModel(option[optionValueKey])"
id="nc-selected-item-icon"
icon="check"
class="flex-none text-primary w-4 h-4"
/>
</slot>
</slot> </slot>
</div> </NcTooltip>
</div> </div>
</div> </div>
</div> </div>

2
packages/nc-gui/components/smartsheet/column/ButtonOptions.vue

@ -365,7 +365,7 @@ const selectIcon = (icon: string) => {
isButtonIconDropdownOpen.value = false isButtonIconDropdownOpen.value = false
} }
const handleUpdateActionType = (type: ButtonActionsType) => { const handleUpdateActionType = () => {
vModel.value.formula_raw = '' vModel.value.formula_raw = ''
} }

53
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type ColumnReqType, type ColumnType, isAIPromptCol } from 'nocodb-sdk' import { type ColumnReqType, type ColumnType, isAIPromptCol, isSupportedDisplayValueColumn } from 'nocodb-sdk'
import { import {
ButtonActionsType, ButtonActionsType,
UITypes, UITypes,
@ -10,7 +10,7 @@ import {
isVirtualCol, isVirtualCol,
readonlyMetaAllowedTypes, readonlyMetaAllowedTypes,
} from 'nocodb-sdk' } from 'nocodb-sdk'
import { AiWizardTabsType, type PredictedFieldType } from '#imports' import { AiWizardTabsType, type PredictedFieldType, type UiTypesType } from '#imports'
import MdiPlusIcon from '~icons/mdi/plus-circle-outline' import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline' import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
import MdiIdentifierIcon from '~icons/mdi/identifier' import MdiIdentifierIcon from '~icons/mdi/identifier'
@ -195,7 +195,7 @@ const predictedFieldType = ref<UITypes | null>(null)
// const lastPredictedAt = ref<number>(0) // const lastPredictedAt = ref<number>(0)
const uiTypesOptions = computed<typeof uiTypes>(() => { const uiTypesOptions = computed<(UiTypesType & { disabled?: boolean; tooltip?: string })[]>(() => {
const types = [ const types = [
...uiTypes.filter(uiFilters), ...uiTypes.filter(uiFilters),
...(!isEdit.value && meta?.value?.columns?.every((c) => !c.pk) ...(!isEdit.value && meta?.value?.columns?.every((c) => !c.pk)
@ -236,7 +236,25 @@ const uiTypesOptions = computed<typeof uiTypes>(() => {
} }
} }
return types if (!isEdit.value) {
return types
} else {
return types.map((type) => {
if (!isEdit.value) return type
const isColumnTypeDisabled =
!!column.value?.pv && column.value?.uidt !== type.name && !isSupportedDisplayValueColumn({ uidt: type.name as UITypes })
return {
...type,
disabled: isColumnTypeDisabled,
tooltip:
isColumnTypeDisabled && UITypesName[type.name]
? `${UITypesName[type.name]} field cannot be used as display value field`
: '',
}
})
}
}) })
const editOrAddRef = ref<HTMLDivElement>() const editOrAddRef = ref<HTMLDivElement>()
@ -504,10 +522,7 @@ const submitBtnLabel = computed(() => {
}) })
const filterOption = (input: string, option: { value: UITypes }) => { const filterOption = (input: string, option: { value: UITypes }) => {
return ( return searchCompare([option.value, ...(UITypesName[option.value] ? [UITypesName[option.value]] : [])], input)
option.value.toLowerCase().includes(input.toLowerCase()) ||
(UITypesName[option.value] && UITypesName[option.value].toLowerCase().includes(input.toLowerCase()))
)
} }
const triggerDescriptionEnable = () => { const triggerDescriptionEnable = () => {
@ -1077,7 +1092,7 @@ watch(activeAiTab, (newValue) => {
v-for="opt of uiTypesOptions" v-for="opt of uiTypesOptions"
:key="opt.name" :key="opt.name"
:value="opt.name" :value="opt.name"
:disabled="isMetaReadOnly && !readonlyMetaAllowedTypes.includes(opt.name)" :disabled="(isMetaReadOnly && !readonlyMetaAllowedTypes.includes(opt.name)) || opt.disabled"
v-bind="validateInfos.uidt" v-bind="validateInfos.uidt"
:class="{ :class="{
'ant-select-item-option-active-selected': showHoverEffectOnSelectedType && formState.uidt === opt.name, 'ant-select-item-option-active-selected': showHoverEffectOnSelectedType && formState.uidt === opt.name,
@ -1085,7 +1100,15 @@ watch(activeAiTab, (newValue) => {
}" }"
@mouseover="handleResetHoverEffect" @mouseover="handleResetHoverEffect"
> >
<div class="w-full flex gap-2 items-center justify-between" :data-testid="opt.name" :data-title="formState?.type"> <NcTooltip
class="w-full flex gap-2 items-center justify-between"
placement="right"
:disabled="!opt?.tooltip"
:attrs="{
'data-testid': opt.name,
}"
>
<template #title> {{ opt?.tooltip }} </template>
<div class="flex-1 flex gap-2 items-center max-w-[calc(100%_-_24px)]"> <div class="flex-1 flex gap-2 items-center max-w-[calc(100%_-_24px)]">
<component <component
:is=" :is="
@ -1095,8 +1118,7 @@ watch(activeAiTab, (newValue) => {
? iconMap.cellAi ? iconMap.cellAi
: opt.icon : opt.icon
" "
class="nc-field-type-icon w-4 h-4" class="nc-field-type-icon w-4 h-4 !opacity-90 text-current"
:class="isMetaReadOnly && !readonlyMetaAllowedTypes.includes(opt.name) ? 'text-gray-300' : 'text-gray-700'"
/> />
<div class="flex-1"> <div class="flex-1">
{{ UITypesName[opt.name] }} {{ UITypesName[opt.name] }}
@ -1118,7 +1140,7 @@ watch(activeAiTab, (newValue) => {
'text-nc-content-purple-medium': isAiMode, 'text-nc-content-purple-medium': isAiMode,
}" }"
/> />
</div> </NcTooltip>
</a-select-option> </a-select-option>
</a-select> </a-select>
</NcTooltip> </NcTooltip>
@ -1410,11 +1432,6 @@ watch(activeAiTab, (newValue) => {
} }
} }
:deep(.ant-select-disabled.nc-column-type-input) {
.nc-field-type-icon {
@apply text-current;
}
}
:deep(.ant-select.nc-column-type-input) { :deep(.ant-select.nc-column-type-input) {
.nc-new-field-badge { .nc-new-field-badge {
@apply hidden; @apply hidden;

32
packages/nc-gui/components/smartsheet/column/LongTextOptions.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { isAIPromptCol, UITypes } from 'nocodb-sdk' import { UITypes, UITypesName, isAIPromptCol } from 'nocodb-sdk'
const props = defineProps<{ const props = defineProps<{
modelValue: any modelValue: any
@ -59,6 +59,12 @@ const isEnabledGenerateText = computed({
}, },
}) })
const isPvColumn = computed(() => {
if (!isEdit.value) return false
return !!column.value?.pv
})
const loadViewData = async () => { const loadViewData = async () => {
if (!formattedData.value.length) { if (!formattedData.value.length) {
await loadData(undefined, false) await loadData(undefined, false)
@ -180,10 +186,16 @@ watch(isPreviewEnabled, handleDisableSubmitBtn, {
<template> <template>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<a-form-item> <a-form-item>
<NcTooltip :disabled="!isEnabledGenerateText"> <NcTooltip :disabled="!(isEnabledGenerateText || (isPvColumn && !richMode))">
<template #title> Rich text formatting is not supported when generate text using AI is enabled </template> <template #title>
{{
isPvColumn && !richMode
? `${UITypesName.RichText} field cannot be used as display value field`
: 'Rich text formatting is not supported when generate text using AI is enabled'
}}
</template>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<NcSwitch v-model:checked="richMode" :disabled="isEnabledGenerateText"> <NcSwitch v-model:checked="richMode" :disabled="isEnabledGenerateText || (isPvColumn && !richMode)">
<div class="text-sm text-gray-800 select-none font-semibold"> <div class="text-sm text-gray-800 select-none font-semibold">
{{ $t('labels.enableRichText') }} {{ $t('labels.enableRichText') }}
</div> </div>
@ -194,12 +206,18 @@ watch(isPreviewEnabled, handleDisableSubmitBtn, {
<div v-if="isPromptEnabled" class="relative"> <div v-if="isPromptEnabled" class="relative">
<a-form-item class="flex items-center"> <a-form-item class="flex items-center">
<NcTooltip :disabled="!richMode" class="flex items-center"> <NcTooltip :disabled="!(richMode || (isPvColumn && !isEnabledGenerateText))" class="flex items-center">
<template #title> Generate text using AI is not supported when rich text formatting is enabled </template> <template #title>
{{
isPvColumn && !isEnabledGenerateText
? `${UITypesName.AIPrompt} field cannot be used as display value field`
: 'Generate text using AI is not supported when rich text formatting is enabled'
}}</template
>
<NcSwitch <NcSwitch
v-model:checked="isEnabledGenerateText" v-model:checked="isEnabledGenerateText"
:disabled="richMode" :disabled="richMode || (isPvColumn && !isEnabledGenerateText)"
class="nc-ai-field-generate-text nc-ai-input" class="nc-ai-field-generate-text nc-ai-input"
@change="handleDisableSubmitBtn" @change="handleDisableSubmitBtn"
> >

2
packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue

@ -14,8 +14,6 @@ const searchQuery = ref('')
const { isMetaReadOnly } = useRoles() const { isMetaReadOnly } = useRoles()
const { isFeatureEnabled } = useBetaFeatureToggle()
const filteredOptions = computed( const filteredOptions = computed(
() => options.value?.filter((c) => searchCompare([c.name, UITypesName[c.name]], searchQuery.value)) ?? [], () => options.value?.filter((c) => searchCompare([c.name, UITypesName[c.name]], searchQuery.value)) ?? [],
) )

38
packages/nc-gui/components/smartsheet/header/Menu.vue

@ -1,6 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type ColumnReqType, type ColumnType, partialUpdateAllowedTypes, readonlyMetaAllowedTypes } from 'nocodb-sdk' import {
import { PlanLimitTypes, RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk' type ColumnReqType,
type ColumnType,
columnTypeName,
partialUpdateAllowedTypes,
readonlyMetaAllowedTypes,
} from 'nocodb-sdk'
import { PlanLimitTypes, RelationTypes, UITypes, isLinksOrLTAR, isSupportedDisplayValueColumn, isSystemColumn } from 'nocodb-sdk'
import { SmartsheetStoreEvents, isColumnInvalid } from '#imports' import { SmartsheetStoreEvents, isColumnInvalid } from '#imports'
const props = defineProps<{ virtual?: boolean; isOpen: boolean; isHiddenCol?: boolean }>() const props = defineProps<{ virtual?: boolean; isOpen: boolean; isHiddenCol?: boolean }>()
@ -578,19 +584,25 @@ const onDeleteColumn = () => {
{{ isHiddenCol ? $t('general.showField') : $t('general.hideField') }} {{ isHiddenCol ? $t('general.showField') : $t('general.hideField') }}
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcTooltip
v-if="(!virtual || column?.uidt === UITypes.Formula) && !column?.pv && !isHiddenCol" v-if="column && !column?.pv && !isHiddenCol && (!virtual || column.uidt === UITypes.Formula)"
@click="setAsDisplayValue" :disabled="isSupportedDisplayValueColumn(column)"
> >
<div class="nc-column-set-primary nc-header-menu-item item"> <template #title>
<GeneralLoader v-if="isLoading === 'setDisplay'" size="regular" /> {{ `${columnTypeName(column)} field cannot be used as display value field` }}
<GeneralIcon v-else icon="star" class="text-gray-500 !w-4.25 !h-4.25" /> </template>
<!-- todo : tooltip --> <NcMenuItem :disabled="!isSupportedDisplayValueColumn(column)" @click="setAsDisplayValue">
<!-- Set as Display value --> <div class="nc-column-set-primary nc-header-menu-item item">
{{ $t('activity.setDisplay') }} <GeneralLoader v-if="isLoading === 'setDisplay'" size="regular" />
</div> <GeneralIcon v-else icon="star" class="text-gray-500 !w-4.25 !h-4.25" />
</NcMenuItem>
<!-- todo : tooltip -->
<!-- Set as Display value -->
{{ $t('activity.setDisplay') }}
</div>
</NcMenuItem>
</NcTooltip>
<template v-if="!isExpandedForm"> <template v-if="!isExpandedForm">
<a-divider v-if="!isLinksOrLTAR(column) || column.colOptions.type !== RelationTypes.BELONGS_TO" class="!my-0" /> <a-divider v-if="!isLinksOrLTAR(column) || column.colOptions.type !== RelationTypes.BELONGS_TO" class="!my-0" />

80
packages/nc-gui/components/smartsheet/header/UpdateDisplayValue.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { type ColumnType, isVirtualCol } from 'nocodb-sdk' import { type ColumnType, columnTypeName, isSupportedDisplayValueColumn, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
interface Props { interface Props {
column: ColumnType column: ColumnType
@ -18,13 +18,11 @@ const { fields } = useViewColumnsOrThrow()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
const searchField = ref('')
const column = toRef(props, 'column') const column = toRef(props, 'column')
const value = useVModel(props, 'value') const value = useVModel(props, 'value')
const selectedField = ref() const selectedFieldId = ref()
const isLoading = ref(false) const isLoading = ref(false)
@ -32,15 +30,28 @@ const filteredColumns = computed(() => {
const columns = meta.value?.columnsById ?? {} const columns = meta.value?.columnsById ?? {}
return (fields.value ?? []) return (fields.value ?? [])
.filter((f) => !isVirtualCol(columns[f.fk_column_id])) .filter((f) => columns[f?.fk_column_id] && !isSystemColumn(columns[f.fk_column_id]))
.filter((c) => c.title.toLowerCase().includes(searchField.value.toLowerCase())) .map((f) => {
const column = columns[f.fk_column_id] as ColumnType
return {
title: f.title,
id: f.fk_column_id,
disabled: !isSupportedDisplayValueColumn(column) && !column.pv,
ncItemTooltip:
!isSupportedDisplayValueColumn(column) && columnTypeName(column) && !column.pv
? `${columnTypeName(column)} field cannot be used as display value field`
: '',
column,
}
})
}) })
const changeDisplayField = async () => { const changeDisplayField = async () => {
if (!selectedFieldId.value) return
isLoading.value = true isLoading.value = true
try { try {
await $api.dbTableColumn.primaryColumnSet(selectedField?.value?.fk_column_id as string) await $api.dbTableColumn.primaryColumnSet(selectedFieldId.value)
await getMeta(meta?.value?.id as string, true) await getMeta(meta?.value?.id as string, true)
@ -53,14 +64,13 @@ const changeDisplayField = async () => {
} }
} }
const getIcon = (c: ColumnType) => const cellIcon = (c: ColumnType) =>
h(isVirtualCol(c) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), { h(isVirtualCol(c) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), {
columnMeta: c, columnMeta: c,
}) })
onMounted(() => { onMounted(() => {
searchField.value = '' selectedFieldId.value = fields.value?.find((f) => f.fk_column_id === column.value.id)?.fk_column_id
selectedField.value = fields.value?.find((f) => f.fk_column_id === column.value.id)
}) })
</script> </script>
@ -79,39 +89,23 @@ onMounted(() => {
</div> </div>
</div> </div>
<div class="flex w-full gap-2 justify-between items-center"> <div class="border-1 rounded-lg border-nc-border-gray-medium h-[250px]">
<a-input v-model:value="searchField" class="w-full h-8 flex-1" size="small" :placeholder="$t('placeholder.searchFields')"> <NcList
<template #prefix> v-model:value="selectedFieldId"
<component :is="iconMap.search" class="w-4 text-gray-500 h-4" /> v-model:open="value"
</template> :list="filteredColumns"
</a-input> search-input-placeholder="Search"
</div> option-label-key="title"
option-value-key="id"
<div class="border-1 rounded-md h-[250px] nc-scrollbar-md border-gray-200"> :close-on-select="false"
<div class="!w-auto"
v-for="col in filteredColumns" show-search-always
:key="col.fk_column_id" container-class-name="!max-h-[200px]"
:class="{
'bg-gray-100': selectedField === col,
}"
:data-testid="`nc-display-field-update-menu-${col.title}`"
class="px-3 py-1 flex flex-row items-center rounded-md hover:bg-gray-100"
@click.stop="selectedField = col"
> >
<div class="flex flex-row items-center w-full cursor-pointer truncate ml-1 py-[5px] pr-2"> <template #listItemExtraLeft="{ option }">
<component :is="getIcon(meta.columnsById[col.fk_column_id])" class="!w-3.5 !h-3.5 !text-gray-500" /> <component :is="cellIcon(option.column)" class="!mx-0 opacity-70" />
<NcTooltip class="flex-1 pl-1 pr-2 truncate" show-on-truncate-only> </template>
<template #title> </NcList>
{{ col.title }}
</template>
<template #default>{{ col.title }}</template>
</NcTooltip>
</div>
<div class="flex-1" />
<component :is="iconMap.check" v-if="selectedField === col" class="!w-4 !h-4 !text-brand-500" />
</div>
</div> </div>
<div class="flex w-full gap-2 justify-end"> <div class="flex w-full gap-2 justify-end">
@ -120,7 +114,7 @@ onMounted(() => {
</NcButton> </NcButton>
<NcButton <NcButton
:disabled="!selectedField || selectedField.fk_column_id === column.id" :disabled="!selectedFieldId || selectedFieldId === column.id"
:loading="isLoading" :loading="isLoading"
size="small" size="small"
@click="changeDisplayField" @click="changeDisplayField"

80
packages/nocodb-sdk/src/lib/UITypes.ts

@ -1,7 +1,7 @@
import { ColumnReqType, ColumnType, TableType } from './Api'; import { ColumnReqType, ColumnType, TableType } from './Api';
import { FormulaDataTypes } from './formulaHelpers'; import { FormulaDataTypes } from './formulaHelpers';
import { LongTextAiMetaProp, RelationTypes } from '~/lib/globals'; import { LongTextAiMetaProp, RelationTypes } from '~/lib/globals';
import { parseHelper } from './helperFunctions'; import { parseHelper, ncParseProp } from './helperFunctions';
enum UITypes { enum UITypes {
ID = 'ID', ID = 'ID',
@ -94,6 +94,37 @@ export const UITypesName = {
AIPrompt: 'AI Prompt', AIPrompt: 'AI Prompt',
}; };
export const columnTypeName = (column?: ColumnType) => {
if (!column) return '';
switch (column.uidt) {
case UITypes.LongText: {
if (ncParseProp(column.meta)?.richMode) {
return UITypesName.RichText;
}
if (ncParseProp(column.meta)[LongTextAiMetaProp]) {
return UITypesName.AIPrompt;
}
return UITypesName[column.uidt];
}
case UITypes.Button: {
if (
column.uidt === UITypes.Button &&
(column?.colOptions as any)?.type === 'ai'
) {
return UITypesName.AIButton;
}
return UITypesName[column.uidt];
}
default: {
return column.uidt ? UITypesName[column.uidt] : '';
}
}
};
export const FieldNameFromUITypes: Record<UITypes, string> = { export const FieldNameFromUITypes: Record<UITypes, string> = {
[UITypes.ID]: 'ID', [UITypes.ID]: 'ID',
[UITypes.LinkToAnotherRecord]: '{TableName}', [UITypes.LinkToAnotherRecord]: '{TableName}',
@ -188,12 +219,11 @@ export function isVirtualCol(
].includes(<UITypes>(typeof col === 'object' ? col?.uidt : col)); ].includes(<UITypes>(typeof col === 'object' ? col?.uidt : col));
} }
export function isAIPromptCol( export function isAIPromptCol(col: ColumnReqType | ColumnType) {
col: return (
| ColumnReqType col.uidt === UITypes.LongText &&
| ColumnType parseHelper((col as any)?.meta)?.[LongTextAiMetaProp]
) { );
return col.uidt === UITypes.LongText && parseHelper((col as any)?.meta)?.[LongTextAiMetaProp];
} }
export function isCreatedOrLastModifiedTimeCol( export function isCreatedOrLastModifiedTimeCol(
@ -331,3 +361,39 @@ export const getUITypesForFormulaDataType = (
return []; return [];
} }
}; };
export const isSupportedDisplayValueColumn = (column: Partial<ColumnType>) => {
if (!column?.uidt) return false;
switch (column.uidt) {
case UITypes.SingleLineText:
case UITypes.Date:
case UITypes.DateTime:
case UITypes.Time:
case UITypes.Year:
case UITypes.PhoneNumber:
case UITypes.Email:
case UITypes.URL:
case UITypes.Number:
case UITypes.Currency:
case UITypes.Percent:
case UITypes.Duration:
case UITypes.Decimal:
case UITypes.Formula: {
return true;
}
case UITypes.LongText: {
if (
ncParseProp(column.meta)?.richMode ||
ncParseProp(column.meta)[LongTextAiMetaProp]
) {
return false;
}
return true;
}
default: {
return false;
}
}
};

20
packages/nocodb-sdk/src/lib/helperFunctions.ts

@ -228,6 +228,24 @@ export const integrationCategoryNeedDefault = (category: IntegrationsType) => {
return [IntegrationsType.Ai].includes(category); return [IntegrationsType.Ai].includes(category);
}; };
export function ncParseProp(v: any): any {
if (!v) return {};
try {
return typeof v === 'string' ? JSON.parse(v) ?? {} : v;
} catch {
return {};
}
}
export function ncStringifyProp(v: any): string {
if (!v) return '{}';
try {
return typeof v === 'string' ? v : JSON.stringify(v) ?? '{}';
} catch {
return '{}';
}
}
export function parseHelper(v: any): any { export function parseHelper(v: any): any {
try { try {
return typeof v === 'string' ? JSON.parse(v) : v; return typeof v === 'string' ? JSON.parse(v) : v;
@ -238,7 +256,7 @@ export function parseHelper(v: any): any {
export function stringifyHelper(v: any): string { export function stringifyHelper(v: any): string {
try { try {
return JSON.stringify(v); return typeof v === 'string' ? v : JSON.stringify(v);
} catch { } catch {
return v; return v;
} }

2
packages/nocodb-sdk/src/lib/index.ts

@ -24,6 +24,8 @@ export {
getUITypesForFormulaDataType, getUITypesForFormulaDataType,
readonlyMetaAllowedTypes, readonlyMetaAllowedTypes,
partialUpdateAllowedTypes, partialUpdateAllowedTypes,
isSupportedDisplayValueColumn,
columnTypeName,
} from '~/lib/UITypes'; } from '~/lib/UITypes';
export { default as CustomAPI, FileType } from '~/lib/CustomAPI'; export { default as CustomAPI, FileType } from '~/lib/CustomAPI';
export { default as TemplateGenerator } from '~/lib/TemplateGenerator'; export { default as TemplateGenerator } from '~/lib/TemplateGenerator';

Loading…
Cancel
Save