mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
864 lines
28 KiB
864 lines
28 KiB
<script lang="ts" setup> |
|
import { type ColumnReqType, type ColumnType } from 'nocodb-sdk' |
|
import { |
|
UITypes, |
|
UITypesName, |
|
isLinksOrLTAR, |
|
isSelfReferencingTableColumn, |
|
isSystemColumn, |
|
isVirtualCol, |
|
readonlyMetaAllowedTypes, |
|
} from 'nocodb-sdk' |
|
import MdiPlusIcon from '~icons/mdi/plus-circle-outline' |
|
import MdiMinusIcon from '~icons/mdi/minus-circle-outline' |
|
import MdiIdentifierIcon from '~icons/mdi/identifier' |
|
|
|
const props = defineProps<{ |
|
preload?: Partial<ColumnType> |
|
columnPosition?: Pick<ColumnReqType, 'column_order'> |
|
// Disable styles like border, shadow to be embedded on other components |
|
embedMode?: boolean |
|
// Will be used to show where ever text 'Column' is used. |
|
// i.e 'Column Name' label in form, thus will be of form `${columnLabel} Name` |
|
columnLabel?: string |
|
hideTitle?: boolean |
|
hideType?: boolean |
|
hideAdditionalOptions?: boolean |
|
fromTableExplorer?: boolean |
|
editDescription?: boolean |
|
readonly?: boolean |
|
disableTitleFocus?: boolean |
|
}>() |
|
|
|
const emit = defineEmits(['submit', 'cancel', 'mounted', 'add', 'update']) |
|
|
|
const { |
|
formState, |
|
isWebhookCreateModalOpen, |
|
generateNewColumnMeta, |
|
addOrUpdate, |
|
onAlter, |
|
onUidtOrIdTypeChange, |
|
validateInfos, |
|
isEdit, |
|
disableSubmitBtn, |
|
column, |
|
} = useColumnCreateStoreOrThrow() |
|
|
|
const editDescription = toRef(props, 'editDescription') |
|
|
|
const { getMeta } = useMetas() |
|
|
|
const { t } = useI18n() |
|
|
|
const { isMetaReadOnly } = useRoles() |
|
|
|
const columnLabel = computed(() => props.columnLabel || t('objects.field')) |
|
|
|
const { $e } = useNuxtApp() |
|
|
|
const { appInfo } = useGlobal() |
|
|
|
const { betaFeatureToggleState } = useBetaFeatureToggle() |
|
|
|
const { openedViewsTab } = storeToRefs(useViewsStore()) |
|
|
|
const { predictColumnType: _predictColumnType } = useNocoEe() |
|
|
|
const meta = inject(MetaInj, ref()) |
|
|
|
const isForm = inject(IsFormInj, ref(false)) |
|
|
|
const isKanban = inject(IsKanbanInj, ref(false)) |
|
|
|
const readOnly = computed(() => props.readonly) |
|
|
|
const { isMysql, isMssql, isDatabricks, isXcdbBase } = useBase() |
|
|
|
const reloadDataTrigger = inject(ReloadViewDataHookInj) |
|
|
|
const advancedOptions = ref(false) |
|
|
|
const mounted = ref(false) |
|
|
|
const showDefaultValueInput = ref(false) |
|
|
|
const showHoverEffectOnSelectedType = ref(true) |
|
|
|
const isVisibleDefaultValueInput = computed({ |
|
get: () => { |
|
if (isValidValue(formState.value.cdf) && !showDefaultValueInput.value) { |
|
showDefaultValueInput.value = true |
|
} |
|
|
|
return isValidValue(formState.value.cdf) || showDefaultValueInput.value |
|
}, |
|
set: (value: boolean) => { |
|
showDefaultValueInput.value = value |
|
}, |
|
}) |
|
|
|
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber] |
|
|
|
const onlyNameUpdateOnEditColumns = [ |
|
UITypes.LinkToAnotherRecord, |
|
UITypes.Lookup, |
|
UITypes.Rollup, |
|
UITypes.Links, |
|
UITypes.CreatedTime, |
|
UITypes.LastModifiedTime, |
|
UITypes.CreatedBy, |
|
UITypes.LastModifiedBy, |
|
UITypes.Formula, |
|
UITypes.QrCode, |
|
UITypes.Barcode, |
|
UITypes.Button, |
|
] |
|
|
|
// To close column type dropdown on escape and |
|
// close modal only when the type popup is close |
|
const isColumnTypeOpen = ref(false) |
|
|
|
const geoDataToggleCondition = (t: { name: UITypes }) => { |
|
if (!appInfo.value.ee) return true |
|
|
|
return betaFeatureToggleState.show ? betaFeatureToggleState.show : !t.name.includes(UITypes.GeoData) |
|
} |
|
|
|
const showDeprecated = ref(false) |
|
|
|
const isSystemField = (t: { name: UITypes }) => |
|
[UITypes.CreatedBy, UITypes.CreatedTime, UITypes.LastModifiedBy, UITypes.LastModifiedTime].includes(t.name) |
|
|
|
const uiFilters = (t: { name: UITypes; virtual?: number; deprecated?: boolean }) => { |
|
const systemFiledNotEdited = !isSystemField(t) || formState.value.uidt === t.name || !isEdit.value |
|
const geoDataToggle = geoDataToggleCondition(t) && (!isEdit.value || !t.virtual || t.name === formState.value.uidt) |
|
const specificDBType = t.name === UITypes.SpecificDBType && isXcdbBase(meta.value?.source_id) |
|
const showDeprecatedField = !t.deprecated || showDeprecated.value |
|
|
|
return systemFiledNotEdited && geoDataToggle && !specificDBType && showDeprecatedField |
|
} |
|
|
|
const uiTypesOptions = computed<typeof uiTypes>(() => { |
|
const types = [ |
|
...uiTypes.filter(uiFilters), |
|
...(!isEdit.value && meta?.value?.columns?.every((c) => !c.pk) |
|
? [ |
|
{ |
|
name: UITypes.ID, |
|
icon: MdiIdentifierIcon, |
|
virtual: 0, |
|
}, |
|
] |
|
: []), |
|
] |
|
|
|
// if meta is readonly, move disabled types to the end |
|
if (isMetaReadOnly.value) { |
|
types.sort((a, b) => { |
|
const aDisabled = readonlyMetaAllowedTypes.includes(a.name) |
|
const bDisabled = readonlyMetaAllowedTypes.includes(b.name) |
|
|
|
if (aDisabled && !bDisabled) return -1 |
|
if (!aDisabled && bDisabled) return 1 |
|
|
|
return 0 |
|
}) |
|
} |
|
|
|
return types |
|
}) |
|
|
|
const onSelectType = (uidt: UITypes) => { |
|
formState.value.uidt = uidt |
|
onUidtOrIdTypeChange() |
|
} |
|
|
|
const reloadMetaAndData = async () => { |
|
await getMeta(meta.value?.id as string, true) |
|
|
|
if (!isKanban.value) { |
|
reloadDataTrigger?.trigger() |
|
} |
|
} |
|
|
|
const saving = ref(false) |
|
|
|
const warningVisible = ref(false) |
|
|
|
const saveSubmitted = async () => { |
|
if (readOnly.value) return |
|
|
|
saving.value = true |
|
const saved = await addOrUpdate(reloadMetaAndData, props.columnPosition) |
|
saving.value = false |
|
|
|
if (!saved) return |
|
|
|
// add delay to complete minimize transition |
|
setTimeout(() => { |
|
advancedOptions.value = false |
|
}, 500) |
|
emit('submit') |
|
|
|
if (isForm.value) { |
|
$e('a:form-view:add-new-field') |
|
} |
|
} |
|
|
|
async function onSubmit() { |
|
if (readOnly.value) return |
|
|
|
// Show warning message if user tries to change type of column |
|
if (isEdit.value && formState.value.uidt !== column.value?.uidt) { |
|
warningVisible.value = true |
|
|
|
const { close } = useDialog(resolveComponent('DlgColumnUpdateConfirm'), { |
|
'visible': warningVisible, |
|
'onUpdate:visible': (value) => (warningVisible.value = value), |
|
'saving': saving, |
|
'onSubmit': async () => { |
|
close() |
|
await saveSubmitted() |
|
}, |
|
}) |
|
} else await saveSubmitted() |
|
} |
|
|
|
// focus and select the column name field |
|
const antInput = ref() |
|
watchEffect(() => { |
|
if (antInput.value && formState.value && !readOnly.value) { |
|
// todo: replace setTimeout |
|
setTimeout(() => { |
|
// focus and select input only if active element is not an input or textarea |
|
if ( |
|
(document.activeElement === document.body || |
|
document.activeElement === null || |
|
['BUTTON', 'DIV'].includes(document.activeElement?.tagName)) && |
|
!props.disableTitleFocus |
|
) { |
|
antInput.value?.focus() |
|
antInput.value?.select() |
|
} |
|
}, 300) |
|
} |
|
advancedOptions.value = false |
|
}) |
|
|
|
const enableDescription = ref(false) |
|
|
|
const descInputEl = ref() |
|
|
|
const removeDescription = () => { |
|
formState.value.description = '' |
|
enableDescription.value = false |
|
} |
|
|
|
onMounted(() => { |
|
if (column.value?.description?.length || editDescription.value) { |
|
enableDescription.value = true |
|
} |
|
if (!isEdit.value) { |
|
generateNewColumnMeta(true) |
|
} else { |
|
if (formState.value.pk) { |
|
message.info(t('msg.info.editingPKnotSupported')) |
|
emit('cancel') |
|
} else if (isSystemColumn(formState.value) && !isSelfReferencingTableColumn(formState.value)) { |
|
message.info(t('msg.info.editingSystemKeyNotSupported')) |
|
emit('cancel') |
|
} |
|
} |
|
|
|
if (props.preload) { |
|
const { colOptions, ...others } = props.preload |
|
formState.value = { |
|
...formState.value, |
|
...others, |
|
} |
|
|
|
if (colOptions) { |
|
const meta = formState.value.meta || {} |
|
onUidtOrIdTypeChange() |
|
formState.value = { |
|
...formState.value, |
|
colOptions: { |
|
...colOptions, |
|
}, |
|
meta, |
|
} |
|
} |
|
} else { |
|
formState.value.filters = undefined |
|
} |
|
|
|
// for cases like formula |
|
if (formState.value && !formState.value.column_name && !isLinksOrLTAR(formState.value)) { |
|
formState.value.column_name = formState.value?.title |
|
} |
|
|
|
nextTick(() => { |
|
mounted.value = true |
|
emit('mounted') |
|
if (!isEdit.value) { |
|
if (!formState.value?.temp_id) { |
|
emit('add', formState.value) |
|
} |
|
} |
|
|
|
if (isForm.value && !props.fromTableExplorer && !enableDescription.value) { |
|
setTimeout(() => { |
|
antInput.value?.focus() |
|
antInput.value?.select() |
|
}, 100) |
|
} else if (enableDescription.value) { |
|
setTimeout(() => { |
|
descInputEl.value?.focus() |
|
}, 100) |
|
} |
|
}) |
|
}) |
|
|
|
const handleEscape = (event: KeyboardEvent): void => { |
|
if (isColumnTypeOpen.value || isWebhookCreateModalOpen.value) return |
|
|
|
if (event.key === 'Escape') emit('cancel') |
|
} |
|
|
|
const isFieldsTab = computed(() => { |
|
return openedViewsTab.value === 'field' |
|
}) |
|
|
|
const onDropdownChange = (value: boolean) => { |
|
if (value) { |
|
isColumnTypeOpen.value = value |
|
} else { |
|
showHoverEffectOnSelectedType.value = true |
|
setTimeout(() => { |
|
isColumnTypeOpen.value = value |
|
}, 300) |
|
} |
|
} |
|
|
|
const handleResetHoverEffect = () => { |
|
if (!showHoverEffectOnSelectedType.value) return |
|
|
|
showHoverEffectOnSelectedType.value = false |
|
} |
|
|
|
if (props.fromTableExplorer) { |
|
watch( |
|
formState, |
|
() => { |
|
if (mounted.value) emit('update', formState.value) |
|
}, |
|
{ deep: true }, |
|
) |
|
} |
|
|
|
const submitBtnLabel = computed(() => { |
|
return { |
|
label: `${isEdit.value && !props.columnLabel ? t('general.update') : t('general.save')} ${columnLabel.value}`, |
|
loadingLabel: `${isEdit.value && !props.columnLabel ? t('general.updating') : t('general.saving')} ${columnLabel.value}`, |
|
} |
|
}) |
|
|
|
const filterOption = (input: string, option: { value: UITypes }) => { |
|
return ( |
|
option.value.toLowerCase().includes(input.toLowerCase()) || |
|
(UITypesName[option.value] && UITypesName[option.value].toLowerCase().includes(input.toLowerCase())) |
|
) |
|
} |
|
|
|
const triggerDescriptionEnable = () => { |
|
if (enableDescription.value) { |
|
enableDescription.value = false |
|
} else { |
|
enableDescription.value = true |
|
setTimeout(() => { |
|
descInputEl.value?.focus() |
|
}, 100) |
|
} |
|
} |
|
|
|
const isFullUpdateAllowed = computed(() => { |
|
if (isMetaReadOnly.value && !readonlyMetaAllowedTypes.includes(formState.value?.uidt) && !isVirtualCol(formState.value)) { |
|
return false |
|
} |
|
|
|
return true |
|
}) |
|
</script> |
|
|
|
<template> |
|
<div |
|
v-if="!warningVisible" |
|
class="overflow-auto nc-scrollbar-md max-h-[max(80vh,500px)]" |
|
:class="{ |
|
'bg-white': !props.fromTableExplorer, |
|
'w-[384px]': !props.embedMode, |
|
'min-w-[500px]': formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links, |
|
'!w-[600px]': formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links, |
|
'min-w-[422px] !w-full': isLinksOrLTAR(formState.uidt), |
|
'shadow-lg shadow-gray-300 border-1 border-gray-200 rounded-xl p-5': !embedMode, |
|
}" |
|
@keydown="handleEscape" |
|
@click.stop |
|
> |
|
<a-form |
|
v-model="formState" |
|
no-style |
|
name="column-create-or-edit" |
|
layout="vertical" |
|
data-testid="add-or-edit-column" |
|
class="flex flex-col gap-4" |
|
> |
|
<a-form-item v-if="isFieldsTab" v-bind="validateInfos.title" class="flex flex-grow"> |
|
<div |
|
class="flex flex-grow px-2 py-1 items-center rounded-md bg-gray-100 focus:bg-gray-100 outline-none" |
|
style="outline-style: solid; outline-width: thin" |
|
> |
|
<input |
|
ref="antInput" |
|
v-model="formState.title" |
|
:disabled="readOnly || !isFullUpdateAllowed" |
|
:placeholder="`${$t('objects.field')} ${$t('general.name').toLowerCase()} ${isEdit ? '' : $t('labels.optional')}`" |
|
class="flex flex-grow nc-fields-input text-sm font-semibold outline-none bg-inherit min-h-6" |
|
:contenteditable="true" |
|
@input="formState.userHasChangedTitle = true" |
|
/> |
|
</div> |
|
</a-form-item> |
|
<a-form-item v-if="!props.hideTitle && !isFieldsTab" v-bind="validateInfos.title" :required="false" class="!mb-0"> |
|
<a-input |
|
ref="antInput" |
|
v-model:value="formState.title" |
|
class="nc-column-name-input !rounded-lg" |
|
:placeholder="`${$t('objects.field')} ${$t('general.name').toLowerCase()} ${isEdit ? '' : $t('labels.optional')}`" |
|
:disabled="isKanban || readOnly || !isFullUpdateAllowed" |
|
@input="onAlter(8)" |
|
/> |
|
</a-form-item> |
|
|
|
<div class="flex items-center gap-1"> |
|
<template v-if="!props.hideType && !formState.uidt"> |
|
<SmartsheetColumnUITypesOptionsWithSearch :options="uiTypesOptions" @selected="onSelectType" /> |
|
</template> |
|
|
|
<a-form-item |
|
v-else-if="!props.hideType" |
|
class="flex-1" |
|
@keydown.up.stop="handleResetHoverEffect" |
|
@keydown.down.stop="handleResetHoverEffect" |
|
> |
|
<a-select |
|
v-model:value="formState.uidt" |
|
show-search |
|
class="nc-column-type-input !rounded-lg" |
|
:disabled=" |
|
(isEdit && isMetaReadOnly && !readonlyMetaAllowedTypes.includes(formState.uidt)) || |
|
isKanban || |
|
readOnly || |
|
(isEdit && !!onlyNameUpdateOnEditColumns.includes(column?.uidt)) || |
|
(isEdit && !isFullUpdateAllowed) |
|
" |
|
dropdown-class-name="nc-dropdown-column-type border-1 !rounded-lg border-gray-200" |
|
:filter-option="filterOption" |
|
@dropdown-visible-change="onDropdownChange" |
|
@change="onUidtOrIdTypeChange" |
|
@dblclick="showDeprecated = !showDeprecated" |
|
> |
|
<template #suffixIcon> |
|
<GeneralIcon icon="arrowDown" class="text-gray-700" /> |
|
</template> |
|
<a-select-option |
|
v-for="opt of uiTypesOptions" |
|
:key="opt.name" |
|
:value="opt.name" |
|
:disabled="isMetaReadOnly && !readonlyMetaAllowedTypes.includes(opt.name)" |
|
v-bind="validateInfos.uidt" |
|
:class="{ |
|
'ant-select-item-option-active-selected': showHoverEffectOnSelectedType && formState.uidt === opt.name, |
|
}" |
|
@mouseover="handleResetHoverEffect" |
|
> |
|
<div class="w-full flex gap-2 items-center justify-between" :data-testid="opt.name"> |
|
<div class="flex gap-2 items-center"> |
|
<component |
|
:is="opt.icon" |
|
class="w-4 h-4" |
|
:class="isMetaReadOnly && !readonlyMetaAllowedTypes.includes(opt.name) ? 'text-gray-300' : 'text-gray-700'" |
|
/> |
|
<div class="flex-1">{{ UITypesName[opt.name] }}</div> |
|
<span v-if="opt.deprecated" class="!text-xs !text-gray-300">({{ $t('general.deprecated') }})</span> |
|
</div> |
|
<component |
|
:is="iconMap.check" |
|
v-if="formState.uidt === opt.name" |
|
id="nc-selected-item-icon" |
|
class="text-primary w-4 h-4" |
|
/> |
|
</div> |
|
</a-select-option> |
|
</a-select> |
|
</a-form-item> |
|
<!-- <div v-if="isEeUI && !props.hideType" class="mt-2 cursor-pointer" @click="predictColumnType()"> |
|
<GeneralIcon icon="magic" :class="{ 'nc-animation-pulse': loadMagic }" class="w-full flex mt-2 text-orange-400" /> |
|
</div> --> |
|
</div> |
|
|
|
<template v-if="!readOnly && formState.uidt"> |
|
<SmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" /> |
|
<SmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" /> |
|
<SmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" /> |
|
<SmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" /> |
|
<SmartsheetColumnLongTextOptions v-if="formState.uidt === UITypes.LongText" v-model:value="formState" /> |
|
<SmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" /> |
|
<SmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" /> |
|
<SmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" /> |
|
<SmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" v-model:value="formState" /> |
|
<SmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" /> |
|
<SmartsheetColumnTimeOptions v-if="formState.uidt === UITypes.Time" v-model:value="formState" /> |
|
<SmartsheetColumnNumberOptions v-if="formState.uidt === UITypes.Number" v-model:value="formState" /> |
|
<SmartsheetColumnDecimalOptions v-if="formState.uidt === UITypes.Decimal" v-model:value="formState" /> |
|
<SmartsheetColumnDateTimeOptions |
|
v-if="[UITypes.DateTime, UITypes.CreatedTime, UITypes.LastModifiedTime].includes(formState.uidt)" |
|
v-model:value="formState" |
|
/> |
|
<SmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" /> |
|
<SmartsheetColumnLinkedToAnotherRecordOptions |
|
v-if="formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links" |
|
:key="`${formState.uidt}-${formState.id || 'new'}`" |
|
v-model:value="formState" |
|
:is-edit="isEdit" |
|
/> |
|
<SmartsheetColumnPercentOptions v-if="formState.uidt === UITypes.Percent" v-model:value="formState" /> |
|
<SmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" /> |
|
<SmartsheetColumnUserOptions v-if="formState.uidt === UITypes.User" v-model:value="formState" :is-edit="isEdit" /> |
|
<SmartsheetColumnSelectOptions |
|
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect" |
|
v-model:value="formState" |
|
:from-table-explorer="props.fromTableExplorer || false" |
|
/> |
|
<SmartsheetColumnButtonOptions |
|
v-if="formState.uidt === UITypes.Button" |
|
v-model:value="formState" |
|
:from-table-explorer="props.fromTableExplorer || false" |
|
/> |
|
</template> |
|
<template v-if="formState.uidt"> |
|
<div v-if="formState.meta && columnToValidate.includes(formState.uidt)" class="flex items-center gap-1"> |
|
<NcSwitch v-model:checked="formState.meta.validate" size="small" class="nc-switch"> |
|
<div class="text-sm text-gray-800"> |
|
{{ |
|
`${$t('msg.acceptOnlyValid', { |
|
type: |
|
formState.uidt === UITypes.URL |
|
? `${UITypesName[formState.uidt as UITypes]}s` |
|
: `${UITypesName[formState.uidt as UITypes]}s`.toLowerCase(), |
|
})}` |
|
}} |
|
</div> |
|
</NcSwitch> |
|
</div> |
|
|
|
<template v-if="!readOnly && isFullUpdateAllowed"> |
|
<div class="nc-column-options-wrapper flex flex-col gap-4"> |
|
<!-- |
|
Default Value for JSON & LongText is not supported in MySQL |
|
Default Value is Disabled for MSSQL --> |
|
<LazySmartsheetColumnRichLongTextDefaultValue |
|
v-if="isTextArea(formState) && formState.meta?.richMode" |
|
v-model:value="formState" |
|
v-model:is-visible-default-value-input="isVisibleDefaultValueInput" |
|
/> |
|
<LazySmartsheetColumnDefaultValue |
|
v-else-if=" |
|
!isVirtualCol(formState) && |
|
!isAttachment(formState) && |
|
!isMssql(meta!.source_id) && |
|
!(isMysql(meta!.source_id) && (isJSON(formState) || isTextArea(formState))) && |
|
!(isDatabricks(meta!.source_id) && formState.unique) |
|
" |
|
v-model:value="formState" |
|
v-model:is-visible-default-value-input="isVisibleDefaultValueInput" |
|
/> |
|
|
|
<div |
|
v-if="isDatabricks(meta!.source_id) && !formState.cdf && ![UITypes.MultiSelect, UITypes.Checkbox, UITypes.Rating, UITypes.Attachment, UITypes.Lookup, UITypes.Rollup, UITypes.Formula, UITypes.Barcode, UITypes.QrCode, UITypes.CreatedTime, UITypes.LastModifiedTime, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(formState.uidt)" |
|
class="flex gap-1" |
|
> |
|
<NcSwitch v-model:checked="formState.unique" size="small" class="nc-switch"> |
|
<div class="text-sm text-gray-800">Set as Unique</div> |
|
</NcSwitch> |
|
</div> |
|
</div> |
|
|
|
<div |
|
v-if="!props.hideAdditionalOptions && !isVirtualCol(formState.uidt)&&!(!appInfo.ee && isAttachment(formState)) && (!appInfo.ee || (appInfo.ee && !isXcdbBase(meta!.source_id) && formState.uidt === UITypes.SpecificDBType))" |
|
class="text-xs text-gray-400 flex items-center justify-end" |
|
> |
|
<div |
|
class="nc-more-options flex items-center gap-1 cursor-pointer select-none" |
|
@click="advancedOptions = !advancedOptions" |
|
> |
|
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }} |
|
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" /> |
|
</div> |
|
</div> |
|
|
|
<Transition name="layout" mode="out-in"> |
|
<div v-if="advancedOptions" class="overflow-hidden"> |
|
<LazySmartsheetColumnAttachmentOptions v-if="appInfo.ee && isAttachment(formState)" v-model:value="formState" /> |
|
|
|
<LazySmartsheetColumnAdvancedOptions |
|
v-if="formState.uidt !== UITypes.Attachment" |
|
v-model:value="formState" |
|
:advanced-db-options="advancedOptions || formState.uidt === UITypes.SpecificDBType" |
|
/> |
|
</div> |
|
</Transition> |
|
</template> |
|
|
|
<a-form-item v-if="enableDescription"> |
|
<div class="flex gap-3 text-gray-800 h-7 mb-1 items-center justify-between"> |
|
<span class="text-[13px]"> |
|
{{ $t('labels.description') }} |
|
</span> |
|
|
|
<NcButton type="text" class="!h-6 !w-5" size="xsmall" @click="removeDescription"> |
|
<GeneralIcon icon="delete" class="text-gray-700 w-3.5 h-3.5" /> |
|
</NcButton> |
|
</div> |
|
|
|
<a-textarea |
|
ref="descInputEl" |
|
v-model:value="formState.description" |
|
:class="{ |
|
'!min-h-[200px]': fromTableExplorer, |
|
'h-[150px] !min-h-[100px]': !fromTableExplorer, |
|
}" |
|
class="nc-input-sm nc-input-text-area nc-input-shadow !text-gray-800 px-3 !max-h-[300px]" |
|
hide-details |
|
data-testid="create-field-description-input" |
|
:placeholder="$t('msg.info.enterFieldDescription')" |
|
/> |
|
</a-form-item> |
|
|
|
<template v-if="props.fromTableExplorer"> |
|
<a-form-item> |
|
<NcButton v-if="!enableDescription" size="small" type="text" @click.stop="triggerDescriptionEnable"> |
|
<div class="flex !text-gray-700 items-center gap-2"> |
|
<GeneralIcon icon="plus" class="h-4 w-4" /> |
|
|
|
<span class="first-letter:capitalize"> |
|
{{ $t('labels.addDescription').toLowerCase() }} |
|
</span> |
|
</div> |
|
</NcButton> |
|
</a-form-item> |
|
</template> |
|
<template v-else> |
|
<div class="flex items-center justify-between gap-2"> |
|
<NcButton v-if="!enableDescription" size="small" type="text" @click.stop="triggerDescriptionEnable"> |
|
<div class="flex !text-gray-700 items-center gap-2"> |
|
<GeneralIcon icon="plus" class="h-4 w-4" /> |
|
|
|
<span class="first-letter:capitalize"> |
|
{{ $t('labels.addDescription').toLowerCase() }} |
|
</span> |
|
</div> |
|
</NcButton> |
|
<div v-else></div> |
|
|
|
<a-form-item> |
|
<div |
|
class="flex gap-x-2 justify-end" |
|
:class="{ |
|
'justify-end': !props.embedMode, |
|
}" |
|
> |
|
<!-- Cancel --> |
|
<NcButton size="small" html-type="button" type="secondary" @click="emit('cancel')"> |
|
{{ $t('general.cancel') }} |
|
</NcButton> |
|
|
|
<!-- Save --> |
|
<NcButton |
|
html-type="submit" |
|
type="primary" |
|
:loading="saving" |
|
:disabled="!formState.uidt || disableSubmitBtn" |
|
size="small" |
|
:label="submitBtnLabel.label" |
|
:loading-label="submitBtnLabel.loadingLabel" |
|
data-testid="nc-field-modal-submit-btn" |
|
@click.prevent="onSubmit" |
|
> |
|
{{ submitBtnLabel.label }} |
|
<template #loading> |
|
{{ submitBtnLabel.loadingLabel }} |
|
</template> |
|
</NcButton> |
|
</div> |
|
</a-form-item> |
|
</div> |
|
</template> |
|
</template> |
|
</a-form> |
|
</div> |
|
</template> |
|
|
|
<style lang="scss"> |
|
.nc-dropdown-column-type { |
|
.ant-select-item-option-active-selected { |
|
@apply !bg-gray-100; |
|
} |
|
} |
|
</style> |
|
|
|
<style lang="scss" scoped> |
|
.nc-input-text-area { |
|
@apply !text-gray-800; |
|
padding-block: 8px !important; |
|
} |
|
|
|
.nc-fields-input { |
|
&::placeholder { |
|
@apply font-normal; |
|
} |
|
} |
|
|
|
.nc-column-name-input, |
|
:deep(.nc-formula-input), |
|
:deep(.ant-form-item-control-input-content > input.ant-input) { |
|
&:not(:hover):not(:focus) { |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08); |
|
} |
|
|
|
&:hover:not(:focus) { |
|
@apply border-gray-300; |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.24); |
|
} |
|
} |
|
|
|
:deep(.nc-color-picker-dropdown-trigger), |
|
:deep(.nc-default-value-wrapper) { |
|
@apply transition-all duration-0.3s; |
|
|
|
&:not(:hover):not(:focus-within):not(.shadow-selected) { |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08); |
|
} |
|
|
|
&:hover:not(:focus-within):not(.shadow-selected) { |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.24); |
|
} |
|
} |
|
|
|
:deep(.ant-radio-group .ant-radio-wrapper) { |
|
@apply transition-all duration-0.3s; |
|
|
|
&.ant-radio-wrapper-checked:not(.ant-radio-wrapper-disabled):focus-within { |
|
@apply shadow-selected; |
|
} |
|
|
|
&.ant-radio-wrapper-disabled { |
|
@apply pointer-events-none !bg-[#f5f5f5]; |
|
box-shadow: none; |
|
|
|
&:hover { |
|
box-shadow: none; |
|
} |
|
} |
|
|
|
&:not(.ant-radio-wrapper-disabled):not(:hover):not(:focus-within):not(.shadow-selected) { |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08); |
|
} |
|
|
|
&:hover:not(:focus-within):not(.ant-radio-wrapper-disabled) { |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.24); |
|
} |
|
} |
|
|
|
:deep(.ant-select) { |
|
&:not(.ant-select-disabled):not(:hover):not(.ant-select-focused) .ant-select-selector, |
|
&:not(.ant-select-disabled):hover.ant-select-disabled .ant-select-selector { |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08); |
|
} |
|
|
|
&:hover:not(.ant-select-focused):not(.ant-select-disabled) .ant-select-selector { |
|
@apply border-gray-300; |
|
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.24); |
|
} |
|
|
|
&.ant-select-disabled .ant-select-selector { |
|
box-shadow: none; |
|
} |
|
} |
|
|
|
:deep(.ant-form-item-label > label) { |
|
@apply !text-small !leading-[18px] mb-2 text-gray-700 flex; |
|
|
|
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before { |
|
@apply content-[''] m-0; |
|
} |
|
} |
|
|
|
:deep(.ant-form-item-label) { |
|
@apply !pb-0 text-small leading-[18px] text-gray-700; |
|
} |
|
|
|
:deep(.ant-form-item-control-input) { |
|
@apply !min-h-min; |
|
} |
|
|
|
:deep(.ant-form-item) { |
|
@apply !mb-0; |
|
} |
|
|
|
:deep(.ant-select-selection-item) { |
|
@apply flex items-center; |
|
} |
|
|
|
:deep(.ant-form-item-explain) { |
|
@apply !text-[10px] leading-normal; |
|
|
|
& > div:first-child { |
|
@apply mt-0.5; |
|
} |
|
} |
|
|
|
:deep(.ant-form-item-explain) { |
|
@apply !min-h-[15px]; |
|
} |
|
|
|
:deep(.ant-alert) { |
|
@apply !rounded-lg !bg-transparent !border-none !p-0; |
|
|
|
.ant-alert-message { |
|
@apply text-sm text-gray-800 font-weight-600; |
|
} |
|
|
|
.ant-alert-description { |
|
@apply text-small text-gray-500 font-weight-500; |
|
} |
|
} |
|
|
|
:deep(.ant-select) { |
|
.ant-select-selector { |
|
@apply !rounded-lg; |
|
} |
|
} |
|
|
|
:deep(input::placeholder), |
|
:deep(textarea::placeholder) { |
|
@apply text-gray-500; |
|
} |
|
|
|
.nc-column-options-wrapper { |
|
&:empty { |
|
@apply hidden; |
|
} |
|
} |
|
</style>
|
|
|