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.
201 lines
5.2 KiB
201 lines
5.2 KiB
<script setup lang="ts"> |
|
import type { ComponentPublicInstance } from '@vue/runtime-core' |
|
import type { Form as AntForm, SelectProps } from 'ant-design-vue' |
|
import type { FormType, GalleryType, GridType, KanbanType } from 'nocodb-sdk' |
|
import { message } from 'ant-design-vue' |
|
import { capitalize, inject } from '@vue/runtime-core' |
|
import { UITypes, ViewTypes } from 'nocodb-sdk' |
|
import { useI18n } from 'vue-i18n' |
|
import { |
|
FieldsInj, |
|
MetaInj, |
|
ViewListInj, |
|
computed, |
|
generateUniqueTitle, |
|
nextTick, |
|
reactive, |
|
unref, |
|
useApi, |
|
useVModel, |
|
watch, |
|
} from '#imports' |
|
|
|
interface Props { |
|
modelValue: boolean |
|
type: ViewTypes |
|
title?: string |
|
selectedViewId?: string |
|
} |
|
|
|
interface Emits { |
|
(event: 'update:modelValue', value: boolean): void |
|
(event: 'created', value: GridType | KanbanType | GalleryType | FormType): void |
|
} |
|
|
|
interface Form { |
|
title: string |
|
type: ViewTypes |
|
copy_from_id: string | null |
|
// for kanban view only |
|
grp_column_id: string | null |
|
} |
|
|
|
const props = defineProps<Props>() |
|
|
|
const emits = defineEmits<Emits>() |
|
|
|
const inputEl = $ref<ComponentPublicInstance>() |
|
|
|
const formValidator = $ref<typeof AntForm>() |
|
|
|
const vModel = useVModel(props, 'modelValue', emits) |
|
|
|
const { t } = useI18n() |
|
|
|
const { isLoading: loading, api } = useApi() |
|
|
|
const meta = inject(MetaInj, ref()) |
|
|
|
const viewList = inject(ViewListInj) |
|
|
|
const fields = inject(FieldsInj, ref([])) |
|
|
|
const form = reactive<Form>({ |
|
title: props.title || '', |
|
type: props.type, |
|
copy_from_id: null, |
|
grp_column_id: null, |
|
}) |
|
|
|
const viewNameRules = [ |
|
// name is required |
|
{ required: true, message: `${t('labels.viewName')} ${t('general.required')}` }, |
|
// name is unique |
|
{ |
|
validator: (_: unknown, v: string) => |
|
new Promise((resolve, reject) => { |
|
;(unref(viewList) || []).every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== v) |
|
? resolve(true) |
|
: reject(new Error(`View name should be unique`)) |
|
}), |
|
message: 'View name should be unique', |
|
}, |
|
] |
|
|
|
const groupingFieldColumnRules = [ |
|
// name is required |
|
{ required: true, message: `${t('general.groupingField')} ${t('general.required')}` }, |
|
] |
|
|
|
const typeAlias = computed( |
|
() => |
|
({ |
|
[ViewTypes.GRID]: 'grid', |
|
[ViewTypes.GALLERY]: 'gallery', |
|
[ViewTypes.FORM]: 'form', |
|
[ViewTypes.KANBAN]: 'kanban', |
|
}[props.type]), |
|
) |
|
|
|
watch(vModel, (value) => value && init()) |
|
|
|
watch( |
|
() => props.type, |
|
(newType) => (form.type = newType), |
|
) |
|
|
|
function init() { |
|
form.title = generateUniqueTitle(capitalize(ViewTypes[props.type].toLowerCase()), viewList?.value || [], 'title') |
|
|
|
if (props.selectedViewId) { |
|
form.copy_from_id = props.selectedViewId |
|
} |
|
|
|
nextTick(() => { |
|
const el = inputEl?.$el as HTMLInputElement |
|
|
|
if (el) { |
|
el.focus() |
|
el.select() |
|
} |
|
}) |
|
} |
|
|
|
async function onSubmit() { |
|
const isValid = await formValidator?.validateFields() |
|
|
|
if (isValid && form.type) { |
|
const _meta = unref(meta) |
|
|
|
if (!_meta || !_meta.id) return |
|
|
|
try { |
|
let data: GridType | KanbanType | GalleryType | FormType | null = null |
|
|
|
switch (form.type) { |
|
case ViewTypes.GRID: |
|
data = await api.dbView.gridCreate(_meta.id, form) |
|
break |
|
case ViewTypes.GALLERY: |
|
data = await api.dbView.galleryCreate(_meta.id, form) |
|
break |
|
case ViewTypes.FORM: |
|
data = await api.dbView.formCreate(_meta.id, form) |
|
break |
|
case ViewTypes.KANBAN: |
|
data = await api.dbView.kanbanCreate(_meta.id, form) |
|
} |
|
|
|
if (data) { |
|
// View created successfully |
|
message.success(t('msg.toast.createView')) |
|
|
|
emits('created', data) |
|
} |
|
} catch (e: any) { |
|
message.error(e.message) |
|
} |
|
|
|
vModel.value = false |
|
} |
|
} |
|
|
|
const singleSelectFieldOptions = computed<SelectProps['options']>(() => { |
|
return fields.value |
|
.filter((el) => el.uidt === UITypes.SingleSelect) |
|
.map((field) => { |
|
return { |
|
value: field.id, |
|
label: field.title, |
|
} |
|
}) |
|
}) |
|
</script> |
|
|
|
<template> |
|
<a-modal v-model:visible="vModel" class="!top-[35%]" :confirm-loading="loading" wrap-class-name="nc-modal-view-create"> |
|
<template #title> |
|
{{ $t('general.create') }} <span class="text-capitalize">{{ typeAlias }}</span> {{ $t('objects.view') }} |
|
</template> |
|
|
|
<a-form ref="formValidator" layout="vertical" :model="form"> |
|
<a-form-item :label="$t('labels.viewName')" name="title" :rules="viewNameRules"> |
|
<a-input ref="inputEl" v-model:value="form.title" autofocus @keydown.enter="onSubmit" /> |
|
</a-form-item> |
|
<a-form-item v-if="form.type === ViewTypes.KANBAN" name="grp_column_id" :rules="groupingFieldColumnRules"> |
|
<a-select |
|
v-model:value="form.grp_column_id" |
|
class="w-full nc-kanban-grouping-field-select" |
|
:options="singleSelectFieldOptions" |
|
placeholder="Select a Grouping Field" |
|
not-found-content="No Single Select Field can be found. Please create one first." |
|
/> |
|
</a-form-item> |
|
</a-form> |
|
|
|
<template #footer> |
|
<a-button key="back" @click="vModel = false">{{ $t('general.cancel') }}</a-button> |
|
<a-button key="submit" type="primary" :loading="loading" @click="onSubmit">{{ $t('general.submit') }}</a-button> |
|
</template> |
|
</a-modal> |
|
</template>
|
|
|