mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
6 changed files with 393 additions and 2 deletions
@ -0,0 +1,273 @@
|
||||
<script setup lang="ts"> |
||||
import useTableCreate from '../../composables/useTableCreate' |
||||
import { validateTableName } from '~/utils/validation' |
||||
import useProject from '~/composables/useProject' |
||||
|
||||
const { modelValue } = defineProps<{ modelValue?: boolean }>() |
||||
|
||||
const emit = defineEmits(['update:modelValue']) |
||||
|
||||
const dialogShow = computed({ |
||||
get() { |
||||
return modelValue |
||||
}, |
||||
set(v) { |
||||
emit('update:modelValue', v) |
||||
}, |
||||
}) |
||||
|
||||
const valid = ref(false) |
||||
const isAdvanceOptVisible = ref(false) |
||||
const { table, createTable } = useTableCreate() |
||||
const { tables, project } = useProject() |
||||
|
||||
const prefix = computed(() => project?.value?.prefix || '') |
||||
|
||||
const validateDuplicateAlias = (v: string) => { |
||||
return (tables?.value || []).every((t) => t.title !== (v || '')) || 'Duplicate table alias' |
||||
} |
||||
const validateLeadingOrTrailingWhiteSpace = (v: string) => { |
||||
return !/^\s+|\s+$/.test(v) || 'Leading or trailing whitespace not allowed in table name' |
||||
} |
||||
const validateDuplicate = (v: string) => { |
||||
return (tables?.value || []).every((t) => t.table_name.toLowerCase() !== (v || '').toLowerCase()) || 'Duplicate table name' |
||||
} |
||||
|
||||
/* import { validateTableName } from '~/helpers' |
||||
|
||||
export default { |
||||
name: 'DlgTableCreate', |
||||
props: ['value'], |
||||
data() { |
||||
return { |
||||
isAdvanceOptVisible: false, |
||||
table: { |
||||
name: '', |
||||
columns: ['id', 'title', 'created_at', 'updated_at'], |
||||
}, |
||||
isIdToggleAllowed: false, |
||||
valid: false, |
||||
idType: 'AI', |
||||
idTypes: [ |
||||
{ value: 'AI', text: 'Auto increment number' }, |
||||
{ value: 'AG', text: 'Auto generated string' }, |
||||
], |
||||
} |
||||
}, |
||||
computed: { |
||||
dialogShow: { |
||||
get() { |
||||
return this.value |
||||
}, |
||||
set(v) { |
||||
this.$emit('input', v) |
||||
}, |
||||
}, |
||||
projectPrefix() { |
||||
return this.$store.getters['project/GtrProjectPrefix'] |
||||
}, |
||||
tables() { |
||||
return this.$store.state.project.tables || [] |
||||
}, |
||||
}, |
||||
watch: { |
||||
'table.alias': function (alias) { |
||||
this.$set(this.table, 'name', `${this.projectPrefix || ''}${alias}`) |
||||
}, |
||||
}, |
||||
created() { |
||||
this.populateDefaultTitle() |
||||
}, |
||||
mounted() { |
||||
setTimeout(() => { |
||||
const el = this.$refs.input.$el |
||||
el.querySelector('input').focus() |
||||
el.querySelector('input').select() |
||||
}, 100) |
||||
}, |
||||
|
||||
methods: { |
||||
populateDefaultTitle() { |
||||
let c = 1 |
||||
while (this.tables.some((t) => t.title === `Sheet${c}`)) { |
||||
c++ |
||||
} |
||||
this.$set(this.table, 'alias', `Sheet${c}`) |
||||
}, |
||||
validateTableName(v) { |
||||
return validateTableName(v, this.$store.getters['project/GtrProjectIsGraphql']) |
||||
}, |
||||
validateDuplicateAlias(v) { |
||||
return (this.tables || []).every((t) => t.title !== (v || '')) || 'Duplicate table alias' |
||||
}, |
||||
validateLedingOrTrailingWhiteSpace(v) { |
||||
return !/^\s+|\s+$/.test(v) || 'Leading or trailing whitespace not allowed in table name' |
||||
}, |
||||
validateDuplicate(v) { |
||||
return (this.tables || []).every((t) => t.table_name.toLowerCase() !== (v || '').toLowerCase()) || 'Duplicate table name' |
||||
}, |
||||
onCreateBtnClick() { |
||||
this.$emit('create', { |
||||
...this.table, |
||||
columns: this.table.columns.map((c) => (c === 'id' && this.idType === 'AG' ? 'id_ag' : c)), |
||||
}) |
||||
}, |
||||
}, |
||||
} */ |
||||
</script> |
||||
|
||||
<template> |
||||
<v-dialog |
||||
v-model="dialogShow" |
||||
persistent |
||||
max-width="550" |
||||
@keydown.esc="dialogShow = false" |
||||
@keydown.enter="$emit('create', table)" |
||||
> |
||||
<!-- Create A New Table --> |
||||
<v-card class="elevation-1 backgroundColor nc-create-table-card"> |
||||
<v-form ref="form" v-model="valid"> |
||||
<v-card-title class="primary subheading white--text py-2"> |
||||
{{ $t('activity.createTable') }} |
||||
</v-card-title> |
||||
|
||||
<v-card-text class="py-6 px-10"> |
||||
<!-- hint="Enter table name" --> |
||||
<v-text-field |
||||
ref="input" |
||||
v-model="table.title" |
||||
solo |
||||
flat |
||||
persistent-hint |
||||
dense |
||||
hide-details1 |
||||
:rules="[validateTableName, validateDuplicateAlias]" |
||||
:hint="$t('msg.info.enterTableName')" |
||||
class="mt-4 caption nc-table-name" |
||||
/> |
||||
|
||||
<div class="d-flex justify-end"> |
||||
<div class="grey--text caption pointer" @click="isAdvanceOptVisible = !isAdvanceOptVisible"> |
||||
{{ isAdvanceOptVisible ? 'Hide' : 'Show' }} more |
||||
<v-icon x-small color="grey"> |
||||
{{ isAdvanceOptVisible ? 'mdi-minus-circle-outline' : 'mdi-plus-circle-outline' }} |
||||
</v-icon> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="nc-table-advanced-options" :class="{ active: isAdvanceOptVisible }"> |
||||
<!-- hint="Table name as saved in database" --> |
||||
<v-text-field |
||||
v-if="!projectPrefix" |
||||
v-model="table.table_name" |
||||
solo |
||||
flat |
||||
dense |
||||
persistent-hint |
||||
:rules="[validateDuplicate]" |
||||
:hint="$t('msg.info.tableNameInDb')" |
||||
class="mt-4 caption nc-table-name-alias" |
||||
/> |
||||
|
||||
<div class="mt-5"> |
||||
<label class="add-default-title grey--text"> |
||||
<!-- Add Default Columns --> |
||||
{{ $t('msg.info.addDefaultColumns') }} |
||||
</label> |
||||
|
||||
<div class="d-flex caption justify-space-between align-center"> |
||||
<v-checkbox |
||||
key="chk1" |
||||
v-model="table.columns" |
||||
dense |
||||
class="mt-0" |
||||
color="info" |
||||
hide-details |
||||
value="id" |
||||
@click.capture.prevent.stop=" |
||||
() => { |
||||
$toast.info('ID column is required, you can rename this later if required.').goAway(3000) |
||||
if (!table.columns.includes('id')) { |
||||
table.columns.push('id') |
||||
} |
||||
} |
||||
" |
||||
> |
||||
<template #label> |
||||
<div> |
||||
<span v-if="!isIdToggleAllowed" class="caption" @dblclick="isIdToggleAllowed = true">id</span> |
||||
<v-select |
||||
v-else |
||||
v-model="idType" |
||||
style="max-width: 100px" |
||||
class="caption" |
||||
outlined |
||||
dense |
||||
hide-details |
||||
:items="idTypes" |
||||
/> |
||||
</div> |
||||
</template> |
||||
</v-checkbox> |
||||
<v-checkbox key="chk2" v-model="table.columns" dense class="mt-0" color="info" hide-details value="title"> |
||||
<template #label> |
||||
<span class="caption">title</span> |
||||
</template> |
||||
</v-checkbox> |
||||
<v-checkbox key="chk3" v-model="table.columns" dense class="mt-0" color="info" hide-details value="created_at"> |
||||
<template #label> |
||||
<span class="caption">created_at</span> |
||||
</template> |
||||
</v-checkbox> |
||||
<v-checkbox key="chk4" v-model="table.columns" dense class="mt-0" color="info" hide-details value="updated_at"> |
||||
<template #label> |
||||
<span class="caption">updated_at</span> |
||||
</template> |
||||
</v-checkbox> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</v-card-text> |
||||
<v-divider /> |
||||
<v-card-actions class="py-4 px-10"> |
||||
<v-spacer /> |
||||
<v-btn class="" @click="dialogShow = false"> |
||||
{{ $t('general.cancel') }} |
||||
</v-btn> |
||||
<v-btn :disabled="!valid" color="primary" class="nc-create-table-submit" @click="createTable"> |
||||
{{ $t('general.submit') }} |
||||
</v-btn> |
||||
</v-card-actions> |
||||
</v-form> |
||||
</v-card> |
||||
</v-dialog> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
::v-deep { |
||||
.v-text-field__details { |
||||
padding: 0 2px !important; |
||||
|
||||
.v-messages:not(.error--text) { |
||||
.v-messages__message { |
||||
color: grey; |
||||
font-size: 0.65rem; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.add-default-title { |
||||
font-size: 0.65rem; |
||||
} |
||||
|
||||
.nc-table-advanced-options { |
||||
max-height: 0; |
||||
transition: 0.3s max-height; |
||||
overflow: hidden; |
||||
|
||||
&.active { |
||||
max-height: 200px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,46 @@
|
||||
import { useNuxtApp } from '#app' |
||||
|
||||
export default (onTableCreate?: (tableMeta: any) => void) => { |
||||
const table = reactive<{ title: string; table_name: string }>({ |
||||
title: '', |
||||
table_name: '', |
||||
}) |
||||
|
||||
const { sqlUi, project } = useProject() |
||||
const { $api } = useNuxtApp() |
||||
|
||||
const createTable = async () => { |
||||
if (!sqlUi?.value) return |
||||
const columns = sqlUi?.value?.getNewTableColumns().filter((col) => { |
||||
// if (col.column_name === "id" && newTable.columns.includes("id_ag")) {
|
||||
// Object.assign(col, sqlUi?.value?.getDataTypeForUiType({ uidt: UITypes.ID }, "AG"));
|
||||
//
|
||||
// col.dtxp = sqlUi?.value?.getDefaultLengthForDatatype(col.dt);
|
||||
// col.dtxs = sqlUi?.value?.getDefaultScaleForDatatype(col.dt);
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// return this.nodes.newTable.columns.includes(col.column_name);
|
||||
return true |
||||
}) |
||||
await $api.dbTable.create(project?.value?.id as string, { |
||||
...table, |
||||
columns, |
||||
}) |
||||
|
||||
onTableCreate?.({}) |
||||
} |
||||
|
||||
watch( |
||||
() => table.title, |
||||
(title) => { |
||||
table.table_name = `${project?.value?.prefix || ''}${title}` |
||||
}, |
||||
) |
||||
|
||||
const generateUniqueTitle = () => { |
||||
|
||||
} |
||||
|
||||
return { table, createTable } |
||||
} |
@ -1,2 +1,67 @@
|
||||
export const isEmail = (v: string) => |
||||
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(v) |
||||
|
||||
export function validateTableName(v: string, isGQL = false) { |
||||
if (!v) { |
||||
return 'Table name required' |
||||
} |
||||
|
||||
// GraphQL naming convention
|
||||
// http://spec.graphql.org/June2018/#Name
|
||||
|
||||
if (isGQL) { |
||||
if (/^[_A-Za-z][_0-9A-Za-z]*$/.test(v)) { |
||||
return true |
||||
} |
||||
|
||||
if (/^[^_A-Za-z]/.test(v)) { |
||||
return 'Name should start with an alphabet or _' |
||||
} |
||||
const m = v.match(/[^_A-Za-z\d]/g) |
||||
if (m) { |
||||
return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` |
||||
} |
||||
} else { |
||||
// exclude . / \
|
||||
// rest all characters allowed
|
||||
// https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/acreldb/n0rfg6x1shw0ppn1cwhco6yn09f7.htm#:~:text=By%20default%2C%20MySQL%20encloses%20column,not%20truncate%20a%20longer%20name.
|
||||
const m = v.match(/[./\\]/g) |
||||
if (m) { |
||||
return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` |
||||
} |
||||
|
||||
return true |
||||
} |
||||
} |
||||
|
||||
export function validateColumnName(v: string, isGQL = false) { |
||||
if (!v) { |
||||
return 'Column name required' |
||||
} |
||||
|
||||
// GraphQL naming convention
|
||||
// http://spec.graphql.org/June2018/#Name
|
||||
if (isGQL) { |
||||
if (/^[_A-Za-z][_0-9A-Za-z]*$/.test(v)) { |
||||
return true |
||||
} |
||||
|
||||
if (/^[^_A-Za-z]/.test(v)) { |
||||
return 'Name should start with an alphabet or _' |
||||
} |
||||
const m = v.match(/[^_A-Za-z\d]/g) |
||||
if (m) { |
||||
return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` |
||||
} |
||||
} else { |
||||
// exclude . / \
|
||||
// rest all characters allowed
|
||||
// https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/acreldb/n0rfg6x1shw0ppn1cwhco6yn09f7.htm#:~:text=By%20default%2C%20MySQL%20encloses%20column,not%20truncate%20a%20longer%20name.
|
||||
const m = v.match(/[./\\]/g) |
||||
if (m) { |
||||
return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` |
||||
} |
||||
|
||||
return true |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue