多维表格
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.

307 lines
9.5 KiB

<script lang="ts" setup>
import type { ColumnReqType, ColumnType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import {
IsFormInj,
IsKanbanInj,
MetaInj,
ReloadViewDataHookInj,
computed,
inject,
message,
onMounted,
ref,
uiTypes,
useColumnCreateStoreOrThrow,
useEventListener,
useGlobal,
useI18n,
useMetas,
useNuxtApp,
watchEffect,
} from '#imports'
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'>
}>()
const emit = defineEmits(['submit', 'cancel'])
const { formState, generateNewColumnMeta, addOrUpdate, onAlter, onUidtOrIdTypeChange, validateInfos, isEdit } =
useColumnCreateStoreOrThrow()
const { getMeta } = useMetas()
const { t } = useI18n()
const { $e } = useNuxtApp()
const { appInfo } = useGlobal()
const { betaFeatureToggleState } = useBetaFeatureToggle()
const meta = inject(MetaInj, ref())
const isForm = inject(IsFormInj, ref(false))
const isKanban = inject(IsKanbanInj, ref(false))
const reloadDataTrigger = inject(ReloadViewDataHookInj)
const advancedOptions = ref(false)
const advancedDbOptions = ref(false)
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const onlyNameUpdateOnEditColumns = [UITypes.LinkToAnotherRecord, UITypes.Lookup, UITypes.Rollup, UITypes.Links]
const geoDataToggleCondition = (t: { name: UITypes }) => {
return betaFeatureToggleState.show ? betaFeatureToggleState.show : !t.name.includes(UITypes.GeoData)
}
const showDeprecated = $ref(false)
const uiTypesOptions = computed<typeof uiTypes>(() => {
return [
...uiTypes.filter((t) => geoDataToggleCondition(t) && (!isEdit.value || !t.virtual) && (!t.deprecated || showDeprecated)),
...(!isEdit.value && meta?.value?.columns?.every((c) => !c.pk)
? [
{
name: UITypes.ID,
icon: MdiIdentifierIcon,
virtual: 0,
},
]
: []),
]
})
const reloadMetaAndData = async () => {
await getMeta(meta.value?.id as string, true)
if (!isKanban.value) {
reloadDataTrigger?.trigger()
}
}
const saving = ref(false)
async function onSubmit() {
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')
}
}
// focus and select the column name field
const antInput = ref()
watchEffect(() => {
if (antInput.value && formState.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) {
antInput.value?.focus()
antInput.value?.select()
}
}, 300)
}
advancedOptions.value = false
})
onMounted(() => {
if (!isEdit.value) {
generateNewColumnMeta()
const { colOptions, ...others } = props.preload || {}
formState.value = {
...formState.value,
...others,
}
if (colOptions) {
onUidtOrIdTypeChange()
formState.value = {
...formState.value,
...colOptions,
}
}
} else {
if (formState.value.pk) {
message.info(t('msg.info.editingPKnotSupported'))
emit('cancel')
}
}
// for cases like formula
if (formState.value && !formState.value.column_name) {
formState.value.column_name = formState.value?.title
}
})
useEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
emit('cancel')
}
})
</script>
<template>
<div
class="w-[400px] bg-gray-50 shadow p-4 overflow-auto border"
:class="{ '!w-[600px]': formState.uidt === UITypes.Formula, '!w-[500px]': formState.uidt === UITypes.Attachment }"
@click.stop
>
<a-form v-model="formState" no-style name="column-create-or-edit" layout="vertical" data-testid="add-or-edit-column">
<div class="flex flex-col gap-2">
<a-form-item :label="$t('labels.columnName')" v-bind="validateInfos.title">
<a-input
ref="antInput"
v-model:value="formState.title"
class="nc-column-name-input"
:disabled="isKanban"
@input="onAlter(8)"
/>
</a-form-item>
<a-form-item
v-if="!(isEdit && !!onlyNameUpdateOnEditColumns.find((col) => col === formState.uidt))"
:label="$t('labels.columnType')"
>
<a-select
v-model:value="formState.uidt"
show-search
class="nc-column-type-input"
:disabled="isKanban"
dropdown-class-name="nc-dropdown-column-type"
@change="onUidtOrIdTypeChange"
@dblclick="showDeprecated = !showDeprecated"
>
<a-select-option v-for="opt of uiTypesOptions" :key="opt.name" :value="opt.name" v-bind="validateInfos.uidt">
<div class="flex gap-1 items-center">
<component :is="opt.icon" class="text-grey" />
{{ opt.name }}
<span v-if="opt.deprecated" class="!text-xs !text-gray-300">(Deprecated)</span>
</div>
</a-select-option>
</a-select>
</a-form-item>
<LazySmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" />
<LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" />
<LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" />
<LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
<LazySmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<LazySmartsheetColumnDateTimeOptions v-if="formState.uidt === UITypes.DateTime" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions
v-if="!isEdit && (formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links)"
v-model:value="formState"
/>
<LazySmartsheetColumnLinkOptions v-if="isEdit && formState.uidt === UITypes.Links" v-model:value="formState" />
<LazySmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
<LazySmartsheetColumnSelectOptions
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect"
v-model:value="formState"
/>
</div>
<div
v-if="!isVirtualCol(formState.uidt)"
class="text-xs cursor-pointer text-gray-400 nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
@click="advancedOptions = !advancedOptions"
@dblclick="advancedDbOptions = !advancedDbOptions"
>
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" />
</div>
<Transition name="layout" mode="out-in">
<div v-if="advancedOptions" class="overflow-hidden">
<a-checkbox
v-if="formState.meta && columnToValidate.includes(formState.uidt)"
v-model:checked="formState.meta.validate"
class="ml-1 mb-1"
>
<span class="text-[10px] text-gray-600">
{{ `Accept only valid ${formState.uidt}` }}
</span>
</a-checkbox>
<LazySmartsheetColumnAttachmentOptions
v-if="appInfo.ee && formState.uidt === UITypes.Attachment"
v-model:value="formState"
/>
<LazySmartsheetColumnAdvancedOptions
v-model:value="formState"
:advanced-db-options="advancedDbOptions || formState.uidt === UITypes.SpecificDBType"
/>
</div>
</Transition>
<a-form-item>
<div class="flex justify-end gap-1 mt-4">
<a-button html-type="button" @click="emit('cancel')">
<!-- Cancel -->
{{ $t('general.cancel') }}
</a-button>
<a-button html-type="submit" type="primary" :loading="saving" @click.prevent="onSubmit">
<!-- Save -->
{{ $t('general.save') }}
</a-button>
</div>
</a-form-item>
</a-form>
</div>
</template>
<style scoped>
:deep(.ant-form-item-label > label) {
@apply !text-xs;
}
:deep(.ant-form-item-label) {
@apply !pb-0;
}
:deep(.ant-form-item-control-input) {
@apply !min-h-min;
}
:deep(.ant-form-item) {
@apply !mb-1;
}
:deep(.ant-select-selection-item) {
@apply flex items-center;
}
:deep(.ant-form-item-explain-error) {
@apply !text-[10px];
}
:deep(.ant-form-item-explain) {
@apply !min-h-[15px];
}
</style>