diff --git a/packages/nc-gui-v2/components/cell/Duration.vue b/packages/nc-gui-v2/components/cell/Duration.vue index 3be0e2e786..53b34a77b7 100644 --- a/packages/nc-gui-v2/components/cell/Duration.vue +++ b/packages/nc-gui-v2/components/cell/Duration.vue @@ -1,5 +1,5 @@ @@ -12,6 +14,8 @@ const { tabs, activeTab, closeTab } = useTabs() {{ tab.title }} + + diff --git a/packages/nc-gui-v2/components/dlg/TableCreate.vue b/packages/nc-gui-v2/components/dlg/TableCreate.vue new file mode 100644 index 0000000000..5dd3ab4370 --- /dev/null +++ b/packages/nc-gui-v2/components/dlg/TableCreate.vue @@ -0,0 +1,273 @@ + + + + + + + + + {{ $t('activity.createTable') }} + + + + + + + + + {{ isAdvanceOptVisible ? 'Hide' : 'Show' }} more + + {{ isAdvanceOptVisible ? 'mdi-minus-circle-outline' : 'mdi-plus-circle-outline' }} + + + + + + + + + + + + {{ $t('msg.info.addDefaultColumns') }} + + + + { + $toast.info('ID column is required, you can rename this later if required.').goAway(3000) + if (!table.columns.includes('id')) { + table.columns.push('id') + } + } + " + > + + + id + + + + + + + title + + + + + created_at + + + + + updated_at + + + + + + + + + + + {{ $t('general.cancel') }} + + + {{ $t('general.submit') }} + + + + + + + + diff --git a/packages/nc-gui-v2/composables/useProject.ts b/packages/nc-gui-v2/composables/useProject.ts index 013c0bc74d..9ea67b69f7 100644 --- a/packages/nc-gui-v2/composables/useProject.ts +++ b/packages/nc-gui-v2/composables/useProject.ts @@ -1,3 +1,4 @@ +import { SqlUiFactory } from 'nocodb-sdk' import type { ProjectType, TableType } from 'nocodb-sdk' import { useNuxtApp } from '#app' @@ -22,5 +23,7 @@ export default () => { const isMysql = computed(() => ['mysql', 'mysql2'].includes(project.value?.bases?.[0]?.type || '')) const isPg = computed(() => project.value?.bases?.[0]?.type === 'pg') - return { project, tables, loadProject, loadTables, isMysql, isPg } + const sqlUi = computed(() => SqlUiFactory.create({ client: project.value?.bases?.[0]?.type || '' })) + + return { project, tables, loadProject, loadTables, isMysql, isPg, sqlUi } } diff --git a/packages/nc-gui-v2/composables/useTableCreate.ts b/packages/nc-gui-v2/composables/useTableCreate.ts new file mode 100644 index 0000000000..7c12f91a81 --- /dev/null +++ b/packages/nc-gui-v2/composables/useTableCreate.ts @@ -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 } +} diff --git a/packages/nc-gui-v2/utils/validation.ts b/packages/nc-gui-v2/utils/validation.ts index 448f32f700..52c6052cbb 100644 --- a/packages/nc-gui-v2/utils/validation.ts +++ b/packages/nc-gui-v2/utils/validation.ts @@ -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 + } +}